mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[web] Fix webkit ColorFilter.mode for webkit (flutter/engine#27361)
This commit is contained in:
parent
8a12a40f1c
commit
b6dbaa079a
@ -1,2 +1,2 @@
|
||||
repository: https://github.com/flutter/goldens.git
|
||||
revision: 25fad8d9927e443345c28b408220ded99b65d647
|
||||
revision: 10ed22e7e6a5039b84e8c028828a86a7ff98b0be
|
||||
|
||||
@ -76,6 +76,10 @@ class DomRenderer {
|
||||
html.Element? get sceneHostElement => _sceneHostElement;
|
||||
html.Element? _sceneHostElement;
|
||||
|
||||
/// A child element of body outside the shadowroot that hosts
|
||||
/// global resources such svg filters and clip paths when using webkit.
|
||||
html.Element? _resourcesHost;
|
||||
|
||||
/// The element that contains the semantics tree.
|
||||
///
|
||||
/// This element is created and inserted in the HTML DOM once. It is never
|
||||
@ -275,6 +279,8 @@ class DomRenderer {
|
||||
|
||||
_styleElement?.remove();
|
||||
_styleElement = html.StyleElement();
|
||||
_resourcesHost?.remove();
|
||||
_resourcesHost = null;
|
||||
html.document.head!.append(_styleElement!);
|
||||
final html.CssStyleSheet sheet = _styleElement!.sheet as html.CssStyleSheet;
|
||||
applyGlobalCssRulesToSheet(
|
||||
@ -615,6 +621,33 @@ class DomRenderer {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Add an element as a global resource to be referenced by CSS.
|
||||
///
|
||||
/// This call create a global resource host element on demand and either
|
||||
/// place it as first element of body(webkit), or as a child of
|
||||
/// glass pane element for other browsers to make sure url resolution
|
||||
/// works correctly when content is inside a shadow root.
|
||||
void addResource(html.Element element) {
|
||||
final bool isWebKit = browserEngine == BrowserEngine.webkit;
|
||||
if (_resourcesHost == null) {
|
||||
_resourcesHost = html.DivElement()
|
||||
..style.visibility = 'hidden';
|
||||
if (isWebKit) {
|
||||
final html.Node bodyNode = html.document.body!;
|
||||
bodyNode.insertBefore(_resourcesHost!, bodyNode.firstChild);
|
||||
} else {
|
||||
_glassPaneShadow!.node.insertBefore(
|
||||
_resourcesHost!, _glassPaneShadow!.node.firstChild);
|
||||
}
|
||||
}
|
||||
_resourcesHost!.append(element);
|
||||
}
|
||||
|
||||
/// Removes a global resource element.
|
||||
void removeResource(html.Element? element) {
|
||||
element?.remove();
|
||||
}
|
||||
|
||||
/// Provides haptic feedback.
|
||||
void vibrate(int durationMs) {
|
||||
final html.Navigator navigator = html.window.navigator;
|
||||
|
||||
@ -368,7 +368,7 @@ class BitmapCanvas extends EngineCanvas {
|
||||
/// prefer DOM if canvas has not been allocated yet.
|
||||
///
|
||||
bool _useDomForRenderingFill(SurfacePaintData paint) =>
|
||||
_renderStrategy.isInsideShaderMask ||
|
||||
_renderStrategy.isInsideSvgFilterTree ||
|
||||
(_preserveImageData == false && _contains3dTransform) ||
|
||||
(_childOverdraw &&
|
||||
_canvasPool.canvas == null &&
|
||||
@ -380,7 +380,7 @@ class BitmapCanvas extends EngineCanvas {
|
||||
///
|
||||
/// DOM canvas is generated for simple strokes using borders.
|
||||
bool _useDomForRenderingFillAndStroke(SurfacePaintData paint) =>
|
||||
_renderStrategy.isInsideShaderMask ||
|
||||
_renderStrategy.isInsideSvgFilterTree ||
|
||||
(_preserveImageData == false && _contains3dTransform) ||
|
||||
((_childOverdraw ||
|
||||
_renderStrategy.hasImageElements ||
|
||||
|
||||
@ -243,6 +243,15 @@ class PersistedPhysicalShape extends PersistedContainerSurface
|
||||
return super.createElement()..setAttribute('clip-type', 'physical-shape');
|
||||
}
|
||||
|
||||
@override
|
||||
void discard() {
|
||||
super.discard();
|
||||
_clipElement?.remove();
|
||||
_clipElement = null;
|
||||
_svgElement?.remove();
|
||||
_svgElement = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void apply() {
|
||||
_applyShape();
|
||||
@ -443,6 +452,7 @@ class PersistedPhysicalShape extends PersistedContainerSurface
|
||||
if (_svgElement != null) {
|
||||
rootElement!.insertBefore(_svgElement!, childContainer);
|
||||
}
|
||||
oldSurface._svgElement = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import 'package:ui/ui.dart' as ui;
|
||||
import '../../engine.dart' show NullTreeSanitizer;
|
||||
import '../canvaskit/color_filter.dart';
|
||||
import '../color_filter.dart';
|
||||
import '../dom_renderer.dart';
|
||||
import '../util.dart';
|
||||
import 'bitmap_canvas.dart';
|
||||
import 'path_to_svg_clip.dart';
|
||||
@ -36,12 +37,21 @@ class PersistedColorFilter extends PersistedContainerSurface
|
||||
void adoptElements(PersistedColorFilter oldSurface) {
|
||||
super.adoptElements(oldSurface);
|
||||
_childContainer = oldSurface._childContainer;
|
||||
_filterElement = oldSurface._filterElement;
|
||||
oldSurface._childContainer = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void preroll(PrerollSurfaceContext prerollContext) {
|
||||
++prerollContext.activeColorFilterCount;
|
||||
super.preroll(prerollContext);
|
||||
--prerollContext.activeColorFilterCount;
|
||||
}
|
||||
|
||||
@override
|
||||
void discard() {
|
||||
super.discard();
|
||||
domRenderer.removeResource(_filterElement);
|
||||
// Do not detach the child container from the root. It is permanently
|
||||
// attached. The elements are reused together and are detached from the DOM
|
||||
// together.
|
||||
@ -60,9 +70,8 @@ class PersistedColorFilter extends PersistedContainerSurface
|
||||
|
||||
@override
|
||||
void apply() {
|
||||
if (_filterElement != null) {
|
||||
_filterElement?.remove();
|
||||
}
|
||||
domRenderer.removeResource(_filterElement);
|
||||
_filterElement = null;
|
||||
final EngineColorFilter? engineValue = filter as EngineColorFilter?;
|
||||
if (engineValue == null) {
|
||||
rootElement!.style.backgroundColor = '';
|
||||
@ -81,12 +90,12 @@ class PersistedColorFilter extends PersistedContainerSurface
|
||||
void _applyBlendModeFilter(CkBlendModeColorFilter colorFilter) {
|
||||
final ui.Color filterColor = colorFilter.color;
|
||||
ui.BlendMode colorFilterBlendMode = colorFilter.blendMode;
|
||||
final html.CssStyleDeclaration style = rootElement!.style;
|
||||
final html.CssStyleDeclaration style = childContainer!.style;
|
||||
switch (colorFilterBlendMode) {
|
||||
case ui.BlendMode.clear:
|
||||
case ui.BlendMode.dstOut:
|
||||
case ui.BlendMode.srcOut:
|
||||
childContainer?.style.visibility = 'hidden';
|
||||
style.visibility = 'hidden';
|
||||
return;
|
||||
case ui.BlendMode.dst:
|
||||
case ui.BlendMode.dstIn:
|
||||
@ -130,8 +139,9 @@ class PersistedColorFilter extends PersistedContainerSurface
|
||||
if (svgFilter != null) {
|
||||
_filterElement =
|
||||
html.Element.html(svgFilter, treeSanitizer: NullTreeSanitizer());
|
||||
rootElement!.append(_filterElement!);
|
||||
rootElement!.style.filter = 'url(#_fcf${filterIdCounter})';
|
||||
//rootElement!.insertBefore(_filterElement!, childContainer!);
|
||||
domRenderer.addResource(_filterElement!);
|
||||
style.filter = 'url(#_fcf${filterIdCounter})';
|
||||
if (colorFilterBlendMode == ui.BlendMode.saturation ||
|
||||
colorFilterBlendMode == ui.BlendMode.multiply ||
|
||||
colorFilterBlendMode == ui.BlendMode.modulate) {
|
||||
@ -145,8 +155,8 @@ class PersistedColorFilter extends PersistedContainerSurface
|
||||
if (svgFilter != null) {
|
||||
_filterElement =
|
||||
html.Element.html(svgFilter, treeSanitizer: NullTreeSanitizer());
|
||||
rootElement!.append(_filterElement!);
|
||||
rootElement!.style.filter = 'url(#_fcf${filterIdCounter})';
|
||||
domRenderer.addResource(_filterElement!);
|
||||
childContainer!.style.filter = 'url(#_fcf${filterIdCounter})';
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,7 +230,7 @@ String? svgFilterFromBlendMode(
|
||||
case ui.BlendMode.difference:
|
||||
case ui.BlendMode.exclusion:
|
||||
svgFilter = _blendColorFilterToSvg(
|
||||
filterColor, stringForBlendMode(colorFilterBlendMode));
|
||||
filterColor, stringForBlendMode(colorFilterBlendMode)!);
|
||||
break;
|
||||
case ui.BlendMode.src:
|
||||
case ui.BlendMode.dst:
|
||||
@ -251,7 +261,7 @@ String? svgFilterFromColorMatrix(List<double> matrix) {
|
||||
return '$kSvgResourceHeader'
|
||||
'<filter id="_fcf$filterIdCounter" '
|
||||
'filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">'
|
||||
'<feColorMatrix values="$sbMatrix" result="comp"/>'
|
||||
'<feColorMatrix type="matrix" values="$sbMatrix" result="comp"/>'
|
||||
'</filter></svg>';
|
||||
}
|
||||
|
||||
@ -273,12 +283,12 @@ int filterIdCounter = 0;
|
||||
String _srcInColorFilterToSvg(ui.Color? color) {
|
||||
filterIdCounter += 1;
|
||||
return '$kSvgResourceHeader'
|
||||
'<filter id="_fcf$filterIdCounter" '
|
||||
'<filter id="_fcf$filterIdCounter" color-interpolation-filters="sRGB" '
|
||||
'filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">'
|
||||
'<feColorMatrix values="0 0 0 0 1 ' // Ignore input, set it to absolute.
|
||||
'0 0 0 0 1 '
|
||||
'0 0 0 0 1 '
|
||||
'0 0 0 1 0" result="destalpha"/>' // Just take alpha channel of destination
|
||||
'<feColorMatrix type="matrix" values="0 0 0 0 1\n' // Ignore input, set it to absolute.
|
||||
'0 0 0 0 1\n'
|
||||
'0 0 0 0 1\n'
|
||||
'0 0 0 1 0" result="destalpha"></feColorMatrix>>' // Just take alpha channel of destination
|
||||
'<feFlood flood-color="${colorToCssString(color)}" flood-opacity="1" result="flood">'
|
||||
'</feFlood>'
|
||||
'<feComposite in="flood" in2="destalpha" '
|
||||
@ -363,7 +373,7 @@ String _modulateColorFilterToSvg(ui.Color color) {
|
||||
}
|
||||
|
||||
// Uses feBlend element to blend source image with a color.
|
||||
String _blendColorFilterToSvg(ui.Color? color, String? feBlend,
|
||||
String _blendColorFilterToSvg(ui.Color? color, String feBlend,
|
||||
{bool swapLayers = false}) {
|
||||
filterIdCounter += 1;
|
||||
return '$kSvgResourceHeader'
|
||||
|
||||
@ -18,7 +18,6 @@ import 'bitmap_canvas.dart';
|
||||
import 'debug_canvas_reuse_overlay.dart';
|
||||
import 'dom_canvas.dart';
|
||||
import 'path/path_metrics.dart';
|
||||
import 'shader_mask.dart';
|
||||
import 'surface.dart';
|
||||
import 'surface_stats.dart';
|
||||
|
||||
@ -128,11 +127,12 @@ class PersistedPicture extends PersistedLeafSurface {
|
||||
}
|
||||
|
||||
@override
|
||||
void preroll() {
|
||||
if (PersistedShaderMask.activeShaderMaskCount != 0) {
|
||||
picture.recordingCanvas?.renderStrategy.isInsideShaderMask = true;
|
||||
void preroll(PrerollSurfaceContext prerollContext) {
|
||||
if (prerollContext.activeShaderMaskCount != 0 ||
|
||||
prerollContext.activeColorFilterCount != 0) {
|
||||
picture.recordingCanvas?.renderStrategy.isInsideSvgFilterTree = true;
|
||||
}
|
||||
super.preroll();
|
||||
super.preroll(prerollContext);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -2042,12 +2042,12 @@ class RenderStrategy {
|
||||
/// This is used to decide whether to use simplified DomCanvas.
|
||||
bool hasArbitraryPaint = false;
|
||||
|
||||
/// Whether commands are executed within a shadermask.
|
||||
/// Whether commands are executed within a shadermask or color filter.
|
||||
///
|
||||
/// Webkit doesn't apply filters to canvas elements in its child
|
||||
/// element tree. When this is set to true, we prevent canvas usage in
|
||||
/// bitmap canvas and instead render using dom primitives and svg only.
|
||||
bool isInsideShaderMask = false;
|
||||
bool isInsideSvgFilterTree = false;
|
||||
|
||||
RenderStrategy();
|
||||
|
||||
|
||||
@ -553,7 +553,7 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
// Auto-pop layers that were pushed without a corresponding pop.
|
||||
pop();
|
||||
}
|
||||
_persistedScene.preroll();
|
||||
_persistedScene.preroll(PrerollSurfaceContext());
|
||||
});
|
||||
return timeAction<SurfaceScene>(kProfileApplyFrame, () {
|
||||
if (_lastFrameScene == null) {
|
||||
|
||||
@ -8,6 +8,7 @@ import 'package:ui/ui.dart' as ui;
|
||||
|
||||
import '../../engine.dart' show NullTreeSanitizer;
|
||||
import '../browser_detection.dart';
|
||||
import '../dom_renderer.dart';
|
||||
import 'bitmap_canvas.dart';
|
||||
import 'path_to_svg_clip.dart';
|
||||
import 'shaders/shader.dart';
|
||||
@ -40,7 +41,6 @@ class PersistedShaderMask extends PersistedContainerSurface
|
||||
final ui.BlendMode blendMode;
|
||||
final ui.FilterQuality filterQuality;
|
||||
html.Element? _shaderElement;
|
||||
static int activeShaderMaskCount = 0;
|
||||
final bool isWebKit = browserEngine == BrowserEngine.webkit;
|
||||
|
||||
@override
|
||||
@ -58,6 +58,7 @@ class PersistedShaderMask extends PersistedContainerSurface
|
||||
@override
|
||||
void discard() {
|
||||
super.discard();
|
||||
domRenderer.removeResource(_shaderElement);
|
||||
// Do not detach the child container from the root. It is permanently
|
||||
// attached. The elements are reused together and are detached from the DOM
|
||||
// together.
|
||||
@ -65,10 +66,10 @@ class PersistedShaderMask extends PersistedContainerSurface
|
||||
}
|
||||
|
||||
@override
|
||||
void preroll() {
|
||||
++activeShaderMaskCount;
|
||||
super.preroll();
|
||||
--activeShaderMaskCount;
|
||||
void preroll(PrerollSurfaceContext prerollContext) {
|
||||
++prerollContext.activeShaderMaskCount;
|
||||
super.preroll(prerollContext);
|
||||
--prerollContext.activeShaderMaskCount;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -83,7 +84,7 @@ class PersistedShaderMask extends PersistedContainerSurface
|
||||
|
||||
@override
|
||||
void apply() {
|
||||
_shaderElement?.remove();
|
||||
domRenderer.removeResource(_shaderElement);
|
||||
_shaderElement = null;
|
||||
if (shader is ui.Gradient) {
|
||||
rootElement!.style
|
||||
@ -164,7 +165,7 @@ class PersistedShaderMask extends PersistedContainerSurface
|
||||
} else {
|
||||
rootElement!.style.filter = 'url(#_fmf${_maskFilterIdCounter}';
|
||||
}
|
||||
rootElement!.append(_shaderElement!);
|
||||
domRenderer.addResource(_shaderElement!);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -568,7 +568,7 @@ abstract class PersistedSurface implements ui.EngineLayer {
|
||||
///
|
||||
/// This method recursively walks the surface tree calling `preroll` on all
|
||||
/// descendants.
|
||||
void preroll() {
|
||||
void preroll(PrerollSurfaceContext prerollContext) {
|
||||
recomputeTransformAndClip();
|
||||
}
|
||||
|
||||
@ -651,11 +651,11 @@ abstract class PersistedContainerSurface extends PersistedSurface {
|
||||
}
|
||||
|
||||
@override
|
||||
void preroll() {
|
||||
super.preroll();
|
||||
void preroll(PrerollSurfaceContext prerollContext) {
|
||||
super.preroll(prerollContext);
|
||||
final int length = _children.length;
|
||||
for (int i = 0; i < length; i += 1) {
|
||||
_children[i].preroll();
|
||||
_children[i].preroll(prerollContext);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1227,3 +1227,13 @@ class _PersistedSurfaceMatch {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Data used during preroll to pass rendering hints efficiently to children
|
||||
/// by optimizing (prevent parent lookups) and in cases like svg filters
|
||||
/// drive the decision on whether canvas elements can be used to render.
|
||||
class PrerollSurfaceContext {
|
||||
/// Number of active color filters in parent surfaces.
|
||||
int activeColorFilterCount = 0;
|
||||
/// Number of active shader masks in parent surfaces.
|
||||
int activeShaderMaskCount = 0;
|
||||
}
|
||||
|
||||
@ -158,6 +158,17 @@ void testMain() {
|
||||
|
||||
attachShadow = oldAttachShadow; // Restore ShadowDOM
|
||||
});
|
||||
|
||||
test('should add/remove global resource', () {
|
||||
final DomRenderer renderer = DomRenderer();
|
||||
final html.DivElement resource = html.DivElement();
|
||||
renderer.addResource(resource);
|
||||
final html.Element? resourceRoot = resource.parent;
|
||||
expect(resourceRoot, isNotNull);
|
||||
expect(resourceRoot!.childNodes.length, 1);
|
||||
renderer.removeResource(resource);
|
||||
expect(resourceRoot.childNodes.length, 0);
|
||||
});
|
||||
}
|
||||
|
||||
@JS('Element.prototype.attachShadow')
|
||||
|
||||
@ -143,7 +143,7 @@ void testMain() {
|
||||
expect(picture.updateCount, 0);
|
||||
expect(picture.applyPaintCount, 0);
|
||||
|
||||
scene1.preroll();
|
||||
scene1.preroll(PrerollSurfaceContext());
|
||||
scene1.build();
|
||||
commitScene(scene1);
|
||||
expect(picture.retainCount, 0);
|
||||
@ -162,7 +162,7 @@ void testMain() {
|
||||
opacity.state = PersistedSurfaceState.pendingRetention;
|
||||
clip2.appendChild(opacity);
|
||||
|
||||
scene2.preroll();
|
||||
scene2.preroll(PrerollSurfaceContext());
|
||||
scene2.update(scene1);
|
||||
commitScene(scene2);
|
||||
expect(picture.retainCount, 1);
|
||||
@ -181,7 +181,7 @@ void testMain() {
|
||||
opacity.state = PersistedSurfaceState.pendingRetention;
|
||||
clip3.appendChild(opacity);
|
||||
|
||||
scene3.preroll();
|
||||
scene3.preroll(PrerollSurfaceContext());
|
||||
scene3.update(scene2);
|
||||
commitScene(scene3);
|
||||
expect(picture.retainCount, 2);
|
||||
|
||||
@ -68,6 +68,24 @@ void testMain() async {
|
||||
maxDiffRatePercent: 12.0);
|
||||
});
|
||||
|
||||
/// Regression test for https://github.com/flutter/flutter/issues/85733
|
||||
test('Should apply mode color filter to circles', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final Picture backgroundPicture = _drawBackground();
|
||||
builder.addPicture(Offset.zero, backgroundPicture);
|
||||
builder.pushColorFilter(
|
||||
ColorFilter.mode(
|
||||
Color(0xFFFF0000),
|
||||
BlendMode.srcIn,
|
||||
));
|
||||
final Picture circles1 = _drawTestPictureWithCircles(30, 30);
|
||||
builder.addPicture(Offset.zero, circles1);
|
||||
builder.pop();
|
||||
html.document.body!.append(builder.build().webOnlyRootElement!);
|
||||
await matchGoldenFile('color_filter_mode.png', region: region,
|
||||
maxDiffRatePercent: 12.0);
|
||||
});
|
||||
|
||||
/// Regression test for https://github.com/flutter/flutter/issues/59451.
|
||||
///
|
||||
/// Picture with overlay blend inside a physical shape. Should show image
|
||||
|
||||
@ -33,7 +33,8 @@ void testMain() async {
|
||||
setUp(() async {
|
||||
debugShowClipLayers = true;
|
||||
SurfaceSceneBuilder.debugForgetFrameScene();
|
||||
for (html.Node scene in html.document.querySelectorAll('flt-scene')) {
|
||||
for (html.Node scene in
|
||||
domRenderer.sceneHostElement!.querySelectorAll('flt-scene')) {
|
||||
scene.remove();
|
||||
}
|
||||
initWebGl();
|
||||
@ -147,5 +148,5 @@ void _renderScene(BlendMode blendMode) {
|
||||
builder.addPicture(Offset.zero, circles2);
|
||||
builder.pop();
|
||||
|
||||
html.document.body!.append(builder.build().webOnlyRootElement!);
|
||||
domRenderer.sceneHostElement!.append(builder.build().webOnlyRootElement!);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user