[ios_platform_view] only recycle maskView when the view is applying mutators (flutter/engine#41573)

A mistake was introduced in https://github.com/flutter/engine/pull/39498  where the maskViews are already recycles each frame. 

Sometimes a PlatformView does not need to be re-composite: (https://github.com/flutter/engine/blob/main/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm#L398-L401), so the mask view for such PlatformView should not be recycled.

This PR changed the `recycleMaskViews` API to allow individual maskviews to be recycled. `ApplyMutator` then only recycle the maskView for that particular PlatformView.

The MaskViewPool is also reworked to be simpler. 

- The pool now contains a single set of mask views, there is no index counter needed.
- When a maskView is needed, try to get it from the pool. 
  - If pool is empty, create a new view.
  - If pool has an available maskview, remove it from the pool.
- When a PlatformView starts to `applyMutator`, it removes current the maskView, insert the maskView to the pool.
- When the above PlatformView needs to a maskView, it grabs one from the pool. 

fixes: https://github.com/flutter/flutter/issues/125620

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
Chris Yang 2023-05-16 09:24:05 -07:00 committed by GitHub
parent 2752fe57eb
commit 259217c7aa
13 changed files with 387 additions and 65 deletions

View File

@ -18,8 +18,6 @@
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
#import "flutter/shell/platform/darwin/ios/ios_surface.h"
static const NSUInteger kFlutterClippingMaskViewPoolCapacity = 5;
@implementation UIView (FirstResponder)
- (BOOL)flt_hasFirstResponderInViewHierarchySubtree {
if (self.isFirstResponder) {
@ -447,6 +445,8 @@ void FlutterPlatformViewsController::ClipViewSetMaskView(UIView* clipView) {
clipView.maskView = [mask_view_pool_.get() getMaskViewWithFrame:frame];
}
// This method is only called when the `embedded_view` needs to be re-composited at the current
// frame. See: `CompositeWithParams` for details.
void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators_stack,
UIView* embedded_view,
const SkRect& bounding_rect) {
@ -461,12 +461,10 @@ void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators
NSMutableArray* blurFilters = [[[NSMutableArray alloc] init] autorelease];
FML_DCHECK(!clipView.maskView ||
[clipView.maskView isKindOfClass:[FlutterClippingMaskView class]]);
if (mask_view_pool_.get() == nil) {
mask_view_pool_.reset([[FlutterClippingMaskViewPool alloc]
initWithCapacity:kFlutterClippingMaskViewPoolCapacity]);
if (clipView.maskView) {
[mask_view_pool_.get() insertViewToPoolIfNeeded:(FlutterClippingMaskView*)(clipView.maskView)];
clipView.maskView = nil;
}
[mask_view_pool_.get() recycleMaskViews];
clipView.maskView = nil;
CGFloat screenScale = [UIScreen mainScreen].scale;
auto iter = mutators_stack.Begin();
while (iter != mutators_stack.End()) {
@ -570,6 +568,14 @@ void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators
embedded_view.layer.transform = flutter::GetCATransform3DFromSkMatrix(transformMatrix);
}
// Composite the PlatformView with `view_id`.
//
// Every frame, during the paint traversal of the layer tree, this method is called for all
// the PlatformViews in `views_to_recomposite_`.
//
// Note that `views_to_recomposite_` does not represent all the views in the view hierarchy,
// if a PlatformView does not change its composition parameter from last frame, it is not
// included in the `views_to_recomposite_`.
void FlutterPlatformViewsController::CompositeWithParams(int64_t view_id,
const EmbeddedViewParams& params) {
CGRect frame = CGRectMake(0, 0, params.sizePoints().width(), params.sizePoints().height());

View File

@ -2649,7 +2649,8 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
[[[FlutterClippingMaskViewPool alloc] initWithCapacity:2] autorelease];
FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero];
FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero];
[pool recycleMaskViews];
[pool insertViewToPoolIfNeeded:view1];
[pool insertViewToPoolIfNeeded:view2];
CGRect newRect = CGRectMake(0, 0, 10, 10);
FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:newRect];
FlutterClippingMaskView* view4 = [pool getMaskViewWithFrame:newRect];
@ -2727,10 +2728,6 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
screenScaleMatrix, SkSize::Make(10, 10), stack1);
flutter::MutatorsStack stack2;
auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
screenScaleMatrix, SkSize::Make(10, 10), stack2);
flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1));
flutterPlatformViewsController->CompositeEmbeddedView(1);
UIView* childClippingView1 = gMockPlatformView.superview.superview;
@ -2738,6 +2735,10 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
XCTAssertNotNil(maskView1);
// Composite a new frame.
flutterPlatformViewsController->BeginFrame(SkISize::Make(100, 100));
flutter::MutatorsStack stack2;
auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
screenScaleMatrix, SkSize::Make(10, 10), stack2);
auto embeddedViewParams3 = std::make_unique<flutter::EmbeddedViewParams>(
screenScaleMatrix, SkSize::Make(10, 10), stack2);
flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams3));
@ -2763,6 +2764,77 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
XCTAssertNil(childClippingView1.maskView);
}
- (void)testDifferentClipMaskViewIsUsedForEachView {
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
/*platform=*/thread_task_runner,
/*raster=*/thread_task_runner,
/*ui=*/thread_task_runner,
/*io=*/thread_task_runner);
auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
/*delegate=*/mock_delegate,
/*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
/*platform_views_controller=*/flutterPlatformViewsController,
/*task_runners=*/runners);
FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
[[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease];
flutterPlatformViewsController->RegisterViewFactory(
factory, @"MockFlutterPlatformView",
FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
FlutterResult result = ^(id result) {
};
flutterPlatformViewsController->OnMethodCall(
[FlutterMethodCall
methodCallWithMethodName:@"create"
arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}],
result);
UIView* view1 = gMockPlatformView;
// This overwrites `gMockPlatformView` to another view.
flutterPlatformViewsController->OnMethodCall(
[FlutterMethodCall
methodCallWithMethodName:@"create"
arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
result);
UIView* view2 = gMockPlatformView;
XCTAssertNotNil(gMockPlatformView);
UIView* mockFlutterView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)] autorelease];
flutterPlatformViewsController->SetFlutterView(mockFlutterView);
// Create embedded view params
flutter::MutatorsStack stack1;
// Layer tree always pushes a screen scale factor to the stack
SkMatrix screenScaleMatrix =
SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
stack1.PushTransform(screenScaleMatrix);
// Push a clip rect
SkRect rect = SkRect::MakeXYWH(2, 2, 3, 3);
stack1.PushClipRect(rect);
auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
screenScaleMatrix, SkSize::Make(10, 10), stack1);
flutter::MutatorsStack stack2;
stack2.PushClipRect(rect);
auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
screenScaleMatrix, SkSize::Make(10, 10), stack2);
flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1));
flutterPlatformViewsController->CompositeEmbeddedView(1);
UIView* childClippingView1 = view1.superview.superview;
flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams2));
flutterPlatformViewsController->CompositeEmbeddedView(2);
UIView* childClippingView2 = view2.superview.superview;
UIView* maskView1 = childClippingView1.maskView;
UIView* maskView2 = childClippingView2.maskView;
XCTAssertNotEqual(maskView1, maskView2);
}
// Return true if a correct visual effect view is found. It also implies all the validation in this
// method passes.
//

View File

@ -56,7 +56,7 @@
// in the pool. If there are none available, a new FlutterClippingMaskView is constructed. If the
// capacity is reached, the newly constructed FlutterClippingMaskView is not added to the pool.
//
// Call |recycleMaskViews| to mark all the FlutterClippingMaskViews in the pool available.
// Call |insertViewToPoolIfNeeded:| to return a maskView to the pool.
@interface FlutterClippingMaskViewPool : NSObject
// Initialize the pool with `capacity`. When the `capacity` is reached, a FlutterClippingMaskView is
@ -66,8 +66,8 @@
// Reuse a maskView from the pool, or allocate a new one.
- (FlutterClippingMaskView*)getMaskViewWithFrame:(CGRect)frame;
// Mark all the maskViews available.
- (void)recycleMaskViews;
// Insert the `maskView` into the pool.
- (void)insertViewToPoolIfNeeded:(FlutterClippingMaskView*)maskView;
@end
@ -291,20 +291,12 @@ class FlutterPlatformViewsController {
int CountClips(const MutatorsStack& mutators_stack);
void ClipViewSetMaskView(UIView* clipView);
// Applies the mutators in the mutators_stack to the UIView chain that was constructed by
// `ReconstructClipViewsChain`
//
// Clips are applied to the super view with a CALayer mask. Transforms are applied to the
// current view that's at the head of the chain. For example the following mutators stack [T_1,
// C_2, T_3, T_4, C_5, T_6] where T denotes a transform and C denotes a clip, will result in the
// following UIView tree:
//
// C_2 -> C_5 -> PLATFORM_VIEW
// (PLATFORM_VIEW is a subview of C_5 which is a subview of C_2)
//
// T_1 is applied to C_2, T_3 and T_4 are applied to C_5, and T_6 is applied to PLATFORM_VIEW.
//
// After each clip operation, we update the head to the super view of the current head.
// Clips are applied to the `embedded_view`'s super view(|ChildClippingView|) using a
// |FlutterClippingMaskView|. Transforms are applied to `embedded_view`
//
// The `bounding_rect` is the final bounding rect of the PlatformView
// (EmbeddedViewParams::finalBoundingRect). If a clip mutator's rect contains the final bounding
@ -312,6 +304,7 @@ class FlutterPlatformViewsController {
void ApplyMutators(const MutatorsStack& mutators_stack,
UIView* embedded_view,
const SkRect& bounding_rect);
void CompositeWithParams(int64_t view_id, const EmbeddedViewParams& params);
// Allocates a new FlutterPlatformViewLayer if needed, draws the pixels within the rect from

View File

@ -9,6 +9,7 @@
#import "flutter/shell/platform/darwin/ios/ios_surface.h"
static int kMaxPointsInVerb = 4;
static const NSUInteger kFlutterClippingMaskViewPoolCapacity = 5;
namespace flutter {
@ -26,7 +27,10 @@ FlutterPlatformViewLayer::~FlutterPlatformViewLayer() = default;
FlutterPlatformViewsController::FlutterPlatformViewsController()
: layer_pool_(std::make_unique<FlutterPlatformViewLayerPool>()),
weak_factory_(std::make_unique<fml::WeakPtrFactory<FlutterPlatformViewsController>>(this)){};
weak_factory_(std::make_unique<fml::WeakPtrFactory<FlutterPlatformViewsController>>(this)) {
mask_view_pool_.reset(
[[FlutterClippingMaskViewPool alloc] initWithCapacity:kFlutterClippingMaskViewPoolCapacity]);
};
FlutterPlatformViewsController::~FlutterPlatformViewsController() = default;
@ -458,9 +462,10 @@ static BOOL _preparedOnce = NO;
// The maximum number of `FlutterClippingMaskView` the pool can contain.
// This prevents the pool to grow infinately and limits the maximum memory a pool can use.
@property(assign, nonatomic) NSUInteger capacity;
@property(retain, nonatomic) NSMutableArray<FlutterClippingMaskView*>* pool;
// The index points to the first available FlutterClippingMaskView in the `pool`.
@property(assign, nonatomic) NSUInteger availableIndex;
// The pool contains the views that are available to use.
// The number of items in the pool must not excceds `capacity`.
@property(retain, nonatomic) NSMutableSet<FlutterClippingMaskView*>* pool;
@end
@ -468,48 +473,42 @@ static BOOL _preparedOnce = NO;
- (instancetype)initWithCapacity:(NSInteger)capacity {
if (self = [super init]) {
_pool = [[NSMutableArray alloc] initWithCapacity:capacity];
// Most of cases, there are only one PlatformView in the scene.
// Thus init with the capacity of 1.
_pool = [[NSMutableSet alloc] initWithCapacity:1];
_capacity = capacity;
_availableIndex = 0;
}
return self;
}
- (FlutterClippingMaskView*)getMaskViewWithFrame:(CGRect)frame {
FML_DCHECK(self.availableIndex <= self.capacity);
FML_DCHECK(self.pool.count <= self.capacity);
FlutterClippingMaskView* maskView;
if (self.availableIndex == self.capacity) {
// The pool is full, alloc a new one.
if (self.pool.count == 0) {
// The pool is empty, alloc a new one.
maskView =
[[[FlutterClippingMaskView alloc] initWithFrame:frame
screenScale:[UIScreen mainScreen].scale] autorelease];
return maskView;
}
if (self.availableIndex >= self.pool.count) {
// The pool doesn't have enough maskViews, alloc a new one and add to the pool.
maskView =
[[[FlutterClippingMaskView alloc] initWithFrame:frame
screenScale:[UIScreen mainScreen].scale] autorelease];
[self.pool addObject:maskView];
FML_DCHECK(self.pool.count <= self.capacity);
} else {
// Reuse a maskView from the pool.
maskView = [self.pool objectAtIndex:self.availableIndex];
maskView.frame = frame;
[maskView reset];
}
self.availableIndex++;
maskView = [self.pool anyObject];
maskView.frame = frame;
[maskView reset];
[self.pool removeObject:maskView];
return maskView;
}
- (void)recycleMaskViews {
self.availableIndex = 0;
- (void)insertViewToPoolIfNeeded:(FlutterClippingMaskView*)maskView {
FML_DCHECK(![self.pool containsObject:maskView]);
FML_DCHECK(self.pool.count <= self.capacity);
if (self.pool.count == self.capacity) {
return;
}
[self.pool addObject:maskView];
}
- (void)dealloc {
[_pool release];
_pool = nil;
[super dealloc];
}

View File

@ -57,6 +57,9 @@
684FFF8D29F9C10700281002 /* golden_platform_view_large_cliprrect_iPhone SE (3rd generation)_16.2_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 684FFF7929F9C10600281002 /* golden_platform_view_large_cliprrect_iPhone SE (3rd generation)_16.2_simulator.png */; };
684FFF8E29F9C10700281002 /* golden_spawn_engine_works_iPhone SE (3rd generation)_16.2_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 684FFF7A29F9C10700281002 /* golden_spawn_engine_works_iPhone SE (3rd generation)_16.2_simulator.png */; };
684FFF8F29F9C10700281002 /* golden_platform_view_with_other_backdrop_filter_iPhone SE (3rd generation)_16.2_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 684FFF7B29F9C10700281002 /* golden_platform_view_with_other_backdrop_filter_iPhone SE (3rd generation)_16.2_simulator.png */; };
6860CE252A01B2FF00B68EC5 /* golden_two_platform_view_clip_rrect_iPhone SE (3rd generation)_16.2_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 6860CE222A01B2FF00B68EC5 /* golden_two_platform_view_clip_rrect_iPhone SE (3rd generation)_16.2_simulator.png */; };
6860CE262A01B2FF00B68EC5 /* golden_two_platform_view_clip_rect_iPhone SE (3rd generation)_16.2_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 6860CE232A01B2FF00B68EC5 /* golden_two_platform_view_clip_rect_iPhone SE (3rd generation)_16.2_simulator.png */; };
6860CE272A01B2FF00B68EC5 /* golden_two_platform_view_clip_path_iPhone SE (3rd generation)_16.2_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 6860CE242A01B2FF00B68EC5 /* golden_two_platform_view_clip_path_iPhone SE (3rd generation)_16.2_simulator.png */; };
68A5B63423EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 68A5B63323EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m */; };
68D4017D2564859300ECD91A /* ContinuousTexture.m in Sources */ = {isa = PBXBuildFile; fileRef = 68D4017C2564859300ECD91A /* ContinuousTexture.m */; };
F26F15B8268B6B5600EC54D3 /* iPadGestureTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F26F15B7268B6B5500EC54D3 /* iPadGestureTests.m */; };
@ -174,6 +177,9 @@
684FFF7929F9C10600281002 /* golden_platform_view_large_cliprrect_iPhone SE (3rd generation)_16.2_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_large_cliprrect_iPhone SE (3rd generation)_16.2_simulator.png"; sourceTree = "<group>"; };
684FFF7A29F9C10700281002 /* golden_spawn_engine_works_iPhone SE (3rd generation)_16.2_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_spawn_engine_works_iPhone SE (3rd generation)_16.2_simulator.png"; sourceTree = "<group>"; };
684FFF7B29F9C10700281002 /* golden_platform_view_with_other_backdrop_filter_iPhone SE (3rd generation)_16.2_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_with_other_backdrop_filter_iPhone SE (3rd generation)_16.2_simulator.png"; sourceTree = "<group>"; };
6860CE222A01B2FF00B68EC5 /* golden_two_platform_view_clip_rrect_iPhone SE (3rd generation)_16.2_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_two_platform_view_clip_rrect_iPhone SE (3rd generation)_16.2_simulator.png"; sourceTree = "<group>"; };
6860CE232A01B2FF00B68EC5 /* golden_two_platform_view_clip_rect_iPhone SE (3rd generation)_16.2_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_two_platform_view_clip_rect_iPhone SE (3rd generation)_16.2_simulator.png"; sourceTree = "<group>"; };
6860CE242A01B2FF00B68EC5 /* golden_two_platform_view_clip_path_iPhone SE (3rd generation)_16.2_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_two_platform_view_clip_path_iPhone SE (3rd generation)_16.2_simulator.png"; sourceTree = "<group>"; };
68A5B63323EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PlatformViewGestureRecognizerTests.m; sourceTree = "<group>"; };
68D4017B2564859300ECD91A /* ContinuousTexture.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ContinuousTexture.h; sourceTree = "<group>"; };
68D4017C2564859300ECD91A /* ContinuousTexture.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ContinuousTexture.m; sourceTree = "<group>"; };
@ -302,6 +308,9 @@
F7B464DC2759D02B00079189 /* Goldens */ = {
isa = PBXGroup;
children = (
6860CE242A01B2FF00B68EC5 /* golden_two_platform_view_clip_path_iPhone SE (3rd generation)_16.2_simulator.png */,
6860CE232A01B2FF00B68EC5 /* golden_two_platform_view_clip_rect_iPhone SE (3rd generation)_16.2_simulator.png */,
6860CE222A01B2FF00B68EC5 /* golden_two_platform_view_clip_rrect_iPhone SE (3rd generation)_16.2_simulator.png */,
684FFF7229F9C10500281002 /* golden_bogus_font_text_iPhone SE (3rd generation)_16.2_simulator.png */,
684FFF7029F9C10500281002 /* golden_non_full_screen_flutter_view_platform_view_iPhone SE (3rd generation)_16.2_simulator.png */,
684FFF7729F9C10600281002 /* golden_platform_view_clippath_iPhone SE (3rd generation)_16.2_simulator.png */,
@ -456,7 +465,9 @@
684FFF7D29F9C10700281002 /* golden_platform_view_cliprect_with_transform_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
684FFF8829F9C10700281002 /* golden_platform_view_cliprect_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
684FFF8529F9C10700281002 /* golden_platform_view_cliprrect_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
6860CE252A01B2FF00B68EC5 /* golden_two_platform_view_clip_rrect_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
684FFF8F29F9C10700281002 /* golden_platform_view_with_other_backdrop_filter_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
6860CE262A01B2FF00B68EC5 /* golden_two_platform_view_clip_rect_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
684FFF8A29F9C10700281002 /* golden_platform_view_clippath_with_transform_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
684FFF8429F9C10700281002 /* golden_non_full_screen_flutter_view_platform_view_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
684FFF7C29F9C10700281002 /* golden_platform_view_cliprrect_with_transform_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
@ -469,6 +480,7 @@
684FFF8D29F9C10700281002 /* golden_platform_view_large_cliprrect_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
684FFF8329F9C10700281002 /* golden_platform_view_transform_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
684FFF8B29F9C10700281002 /* golden_platform_view_clippath_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
6860CE272A01B2FF00B68EC5 /* golden_two_platform_view_clip_path_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
684FFF8129F9C10700281002 /* golden_platform_view_large_cliprrect_with_transform_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -69,7 +69,10 @@
@"--spawn-engine-works" : @"spawn_engine_works",
@"--pointer-events" : @"pointer_events",
@"--platform-view-scrolling-under-widget" : @"platform_view_scrolling_under_widget",
@"--platform-view-cliprect-after-moved" : @"platform_view_cliprect_after_moved"
@"--platform-view-cliprect-after-moved" : @"platform_view_cliprect_after_moved",
@"--two-platform-view-clip-rect" : @"two_platform_view_clip_rect",
@"--two-platform-view-clip-rrect" : @"two_platform_view_clip_rrect",
@"--two-platform-view-clip-path" : @"two_platform_view_clip_path",
};
__block NSString* flutterViewControllerTestName = nil;
[launchArgsMap

View File

@ -47,6 +47,9 @@ NSDictionary* launchArgsMap;
@"--bogus-font-text" : @"bogus_font_text",
@"--spawn-engine-works" : @"spawn_engine_works",
@"--platform-view-cliprect-after-moved" : @"platform_view_cliprect_after_moved",
@"--two-platform-view-clip-rect" : @"two_platform_view_clip_rect",
@"--two-platform-view-clip-rrect" : @"two_platform_view_clip_rrect",
@"--two-platform-view-clip-path" : @"two_platform_view_clip_path",
};
});
_identifier = launchArgsMap[launchArg];

View File

@ -254,6 +254,60 @@ static const NSInteger kSecondsToWaitForPlatformView = 30;
@end
@interface TwoPlatformViewClipRectTests : GoldenPlatformViewTests
@end
@implementation TwoPlatformViewClipRectTests
- (instancetype)initWithInvocation:(NSInvocation*)invocation {
GoldenTestManager* manager =
[[GoldenTestManager alloc] initWithLaunchArg:@"--two-platform-view-clip-rect"];
return [super initWithManager:manager invocation:invocation];
}
- (void)testPlatformView {
[self checkPlatformViewGolden];
}
@end
@interface TwoPlatformViewClipRRectTests : GoldenPlatformViewTests
@end
@implementation TwoPlatformViewClipRRectTests
- (instancetype)initWithInvocation:(NSInvocation*)invocation {
GoldenTestManager* manager =
[[GoldenTestManager alloc] initWithLaunchArg:@"--two-platform-view-clip-rrect"];
return [super initWithManager:manager invocation:invocation];
}
- (void)testPlatformView {
[self checkPlatformViewGolden];
}
@end
@interface TwoPlatformViewClipPathTests : GoldenPlatformViewTests
@end
@implementation TwoPlatformViewClipPathTests
- (instancetype)initWithInvocation:(NSInvocation*)invocation {
GoldenTestManager* manager =
[[GoldenTestManager alloc] initWithLaunchArg:@"--two-platform-view-clip-path"];
return [super initWithManager:manager invocation:invocation];
}
- (void)testPlatformView {
[self checkPlatformViewGolden];
}
@end
@interface PlatformViewMutationTransformTests : GoldenPlatformViewTests
@end

View File

@ -112,7 +112,6 @@ class PlatformViewNoOverlayIntersectionScenario extends Scenario
}
}
/// A platform view that is larger than the display size.
/// This is only applicable on Android while using virtual displays.
/// Related issue: https://github.com/flutter/flutter/issues/28978.
@ -576,7 +575,7 @@ class PlatformViewClipRectAfterMovedScenario extends Scenario with _BasePlatform
..pushClipRect(const Rect.fromLTRB(100, 100, 400, 400));
addPlatformView(
_numberOfFrames == 10? 10000:id,
_numberOfFrames == 10? 10000: id,
dispatcher: view.platformDispatcher,
sceneBuilder: builder,
);
@ -639,7 +638,6 @@ class PlatformViewClipRRectScenario extends PlatformViewScenario {
}
}
/// Platform view with clip rrect.
/// The bounding rect of the rrect is the same as PlatformView and only the corner radii clips the PlatformView.
class PlatformViewLargeClipRRectScenario extends PlatformViewScenario {
@ -878,6 +876,187 @@ class PlatformViewClipPathWithTransformScenario extends PlatformViewScenario {
}
}
/// Two platform views, both have clip rects
class TwoPlatformViewClipRect extends Scenario
with _BasePlatformViewScenarioMixin {
/// Creates the PlatformView scenario.
TwoPlatformViewClipRect(
super.view, {
required this.firstId,
required this.secondId,
});
/// The platform view identifier to use for the first platform view.
final int firstId;
/// The platform view identifier to use for the second platform view.
final int secondId;
@override
void onBeginFrame(Duration duration) {
final SceneBuilder builder = SceneBuilder();
builder.pushOffset(0, 600);
builder.pushClipRect(const Rect.fromLTRB(100, 100, 400, 400));
addPlatformView(
firstId,
dispatcher: view.platformDispatcher,
sceneBuilder: builder,
text: 'platform view 1',
);
builder.pop();
builder.pop();
// Use a different rect to differentiate from the 1st clip rect.
builder.pushClipRect(const Rect.fromLTRB(100, 100, 300, 300));
addPlatformView(
secondId,
dispatcher: view.platformDispatcher,
sceneBuilder: builder,
text: 'platform view 2',
);
builder.pop();
final Scene scene = builder.build();
view.render(scene);
scene.dispose();
}
}
/// Two platform views, both have clip rrects
class TwoPlatformViewClipRRect extends Scenario
with _BasePlatformViewScenarioMixin {
/// Creates the PlatformView scenario.
TwoPlatformViewClipRRect(
super.view, {
required this.firstId,
required this.secondId,
});
/// The platform view identifier to use for the first platform view.
final int firstId;
/// The platform view identifier to use for the second platform view.
final int secondId;
@override
void onBeginFrame(Duration duration) {
final SceneBuilder builder = SceneBuilder();
builder.pushOffset(0, 600);
builder.pushClipRRect(
RRect.fromLTRBAndCorners(
0,
0,
500,
500,
topLeft: const Radius.circular(15),
topRight: const Radius.circular(50),
bottomLeft: const Radius.circular(50),
),
);
addPlatformView(
firstId,
dispatcher: view.platformDispatcher,
sceneBuilder: builder,
text: 'platform view 1',
);
builder.pop();
builder.pop();
// Use a different rrect to differentiate from the 1st clip rrect.
builder.pushClipRRect(
RRect.fromLTRBAndCorners(
0,
0,
500,
500,
topLeft: const Radius.circular(100),
topRight: const Radius.circular(50),
bottomLeft: const Radius.circular(50),
),
);
addPlatformView(
secondId,
dispatcher: view.platformDispatcher,
sceneBuilder: builder,
text: 'platform view 2',
);
builder.pop();
final Scene scene = builder.build();
view.render(scene);
scene.dispose();
}
}
/// Two platform views, both have clip path
class TwoPlatformViewClipPath extends Scenario
with _BasePlatformViewScenarioMixin {
/// Creates the PlatformView scenario.
TwoPlatformViewClipPath(
super.view, {
required this.firstId,
required this.secondId,
});
/// The platform view identifier to use for the first platform view.
final int firstId;
/// The platform view identifier to use for the second platform view.
final int secondId;
@override
void onBeginFrame(Duration duration) {
final SceneBuilder builder = SceneBuilder();
builder.pushOffset(0, 600);
final Path path = Path()
..moveTo(100, 100)
..quadraticBezierTo(50, 250, 100, 400)
..lineTo(350, 400)
..cubicTo(400, 300, 300, 200, 350, 100)
..close();
builder.pushClipPath(path);
addPlatformView(
firstId,
dispatcher: view.platformDispatcher,
sceneBuilder: builder,
text: 'platform view 1',
);
builder.pop();
builder.pop();
// Use a different path to differentiate from the 1st clip path.
final Path path2 = Path()
..moveTo(100, 100)
..quadraticBezierTo(100, 150, 100, 400)
..lineTo(350, 350)
..cubicTo(400, 300, 300, 200, 350, 200)
..close();
builder.pushClipPath(path2);
addPlatformView(
secondId,
dispatcher: view.platformDispatcher,
sceneBuilder: builder,
text: 'platform view 2',
);
builder.pop();
final Scene scene = builder.build();
view.render(scene);
scene.dispose();
}
}
/// Platform view with transform.
class PlatformViewTransformScenario extends PlatformViewScenario {
/// Constructs a platform view with transform scenario.
@ -1044,10 +1223,10 @@ class PlatformViewForOverlappingPlatformViewsScenario extends Scenario
/// Creates the PlatformViewForOverlappingPlatformViewsScenario.
PlatformViewForOverlappingPlatformViewsScenario(
super.view, {
required this.foregroundId,
required this.backgroundId,
}) {
super.view, {
required this.foregroundId,
required this.backgroundId,
}) {
_nextFrame = _firstFrame;
}
@ -1151,7 +1330,7 @@ class PlatformViewForOverlappingPlatformViewsScenario extends Scenario
view.platformDispatcher.sendPlatformMessage(
'flutter/platform_views',
message.buffer.asByteData(),
(ByteData? response) {},
(ByteData? response) {},
);
}
}
@ -1360,7 +1539,7 @@ class PlatformViewScrollingUnderWidget extends Scenario
super.view, {
required int firstPlatformViewId,
required int lastPlatformViewId,
}) : _firstPlatformViewId = firstPlatformViewId,
}) : _firstPlatformViewId = firstPlatformViewId,
_lastPlatformViewId = lastPlatformViewId;
final int _firstPlatformViewId;
@ -1450,7 +1629,6 @@ void addPlatformView(
}
final String platformViewKey = '$viewType-$id';
if (_createdPlatformViews.containsKey(platformViewKey)) {
addPlatformViewToSceneBuilder(
id,
@ -1473,7 +1651,6 @@ void addPlatformView(
const int valueString = 7;
const int valueUint8List = 8;
const int valueMap = 13;
final Uint8List message = Uint8List.fromList(<int>[
valueString,
..._encodeString('create'),

View File

@ -53,6 +53,9 @@ Map<String, _ScenarioFactory> _scenarios = <String, _ScenarioFactory>{
'platform_view_gesture_reject_after_touches_ended': (FlutterView view) => PlatformViewForTouchIOSScenario(view, id: _viewId++, accept: false, rejectUntilTouchesEnded: true),
'platform_view_gesture_accept_with_overlapping_platform_views': (FlutterView view) => PlatformViewForOverlappingPlatformViewsScenario(view, foregroundId: _viewId++, backgroundId: _viewId++),
'platform_view_scrolling_under_widget':(FlutterView view) => PlatformViewScrollingUnderWidget(view, firstPlatformViewId: _viewId++, lastPlatformViewId: _viewId+=16),
'two_platform_view_clip_rect': (FlutterView view) => TwoPlatformViewClipRect(view, firstId: _viewId++, secondId: _viewId++),
'two_platform_view_clip_rrect': (FlutterView view) => TwoPlatformViewClipRRect(view, firstId: _viewId++, secondId: _viewId++),
'two_platform_view_clip_path': (FlutterView view) => TwoPlatformViewClipPath(view, firstId: _viewId++, secondId: _viewId++),
'tap_status_bar': (FlutterView view) => TouchesScenario(view),
'initial_route_reply': (FlutterView view) => InitialRouteReply(view),
'platform_view_with_continuous_texture': (FlutterView view) => PlatformViewWithContinuousTexture(view, id: _viewId++),