PlatformViewsController: clear composition_order_ in the beginning of each frame. (#22574) (#22762)
Co-authored-by: Chris Yang <ychris@google.com>
diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
index feb58fb..90786cb 100644
--- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
+++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
@@ -241,13 +241,13 @@
gesture_recognizers_blocking_policies[idString] = gestureRecognizerBlockingPolicy;
}
-void FlutterPlatformViewsController::SetFrameSize(SkISize frame_size) {
+void FlutterPlatformViewsController::BeginFrame(SkISize frame_size) {
+ ResetFrameState();
frame_size_ = frame_size;
}
void FlutterPlatformViewsController::CancelFrame() {
- picture_recorders_.clear();
- composition_order_.clear();
+ ResetFrameState();
}
// TODO(cyanglaz): https://github.com/flutter/flutter/issues/56474
@@ -603,13 +603,6 @@
}
}
-void FlutterPlatformViewsController::EndFrame(
- bool should_resubmit_frame,
- fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
- // Reset the composition order, so next frame starts empty.
- composition_order_.clear();
-}
-
std::shared_ptr<FlutterPlatformViewLayer> FlutterPlatformViewsController::GetLayer(
GrDirectContext* gr_context,
std::shared_ptr<IOSContext> ios_context,
@@ -705,6 +698,11 @@
}
}
+void FlutterPlatformViewsController::ResetFrameState() {
+ picture_recorders_.clear();
+ composition_order_.clear();
+}
+
} // namespace flutter
// This recognizers delays touch events from being dispatched to the responder chain until it failed
diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
index 31a19b2..55a143e 100644
--- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
+++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
@@ -136,8 +136,6 @@
result);
XCTAssertNotNil(gMockPlatformView);
-
- flutterPlatformViewsController->Reset();
}
- (void)testChildClippingViewHitTests {
@@ -213,7 +211,6 @@
CGRect platformViewRectInFlutterView = [gMockPlatformView convertRect:gMockPlatformView.bounds
toView:mockFlutterView];
XCTAssertTrue(CGRectEqualToRect(platformViewRectInFlutterView, CGRectMake(100, 100, 300, 300)));
- flutterPlatformViewsController->Reset();
}
- (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView {
@@ -283,8 +280,6 @@
XCTAssertLessThan(
fabs(platformViewRectInFlutterView.size.height - childClippingView.frame.size.height),
kFloatCompareEpsilon);
-
- flutterPlatformViewsController->Reset();
}
- (void)testClipRect {
@@ -356,7 +351,6 @@
}
}
}
- flutterPlatformViewsController->Reset();
}
- (void)testClipRRect {
@@ -428,7 +422,6 @@
}
}
}
- flutterPlatformViewsController->Reset();
}
- (void)testClipPath {
@@ -501,7 +494,6 @@
}
}
}
- flutterPlatformViewsController->Reset();
}
- (void)testSetFlutterViewControllerAfterCreateCanStillDispatchTouchEvents {
@@ -564,8 +556,6 @@
flutterPlatformViewsController->SetFlutterViewController(mockFlutterViewContoller);
[forwardGectureRecognizer touchesBegan:touches2 withEvent:event2];
OCMVerify([mockFlutterViewContoller touchesBegan:touches2 withEvent:event2]);
-
- flutterPlatformViewsController->Reset();
}
- (void)testSetFlutterViewControllerInTheMiddleOfTouchEventShouldStillAllowGesturesToBeHandled {
@@ -816,8 +806,6 @@
gpu_is_disabled->SetSwitch(false);
XCTAssertTrue(flutterPlatformViewsController->SubmitFrame(
nullptr, nullptr, std::move(mock_surface_submit_false), gpu_is_disabled));
-
- flutterPlatformViewsController->Reset();
}
- (void)
@@ -869,6 +857,59 @@
XCTAssertNil(gMockPlatformView);
}
+- (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder {
+ 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);
+
+ UIView* mockFlutterView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)] autorelease];
+ flutterPlatformViewsController->SetFlutterView(mockFlutterView);
+
+ FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
+ [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease];
+ flutterPlatformViewsController->RegisterViewFactory(
+ factory, @"MockFlutterPlatformView",
+ FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
+ FlutterResult result = ^(id result) {
+ };
+
+ flutterPlatformViewsController->OnMethodCall(
+ [FlutterMethodCall
+ methodCallWithMethodName:@"create"
+ arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}],
+ result);
+
+ // First frame, |GetCurrentCanvases| is not empty after composite.
+ flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300));
+ flutter::MutatorsStack stack;
+ SkMatrix finalMatrix;
+ auto embeddedViewParams1 =
+ std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
+ flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams1));
+ flutterPlatformViewsController->CompositeEmbeddedView(0);
+ XCTAssertEqual(flutterPlatformViewsController->GetCurrentCanvases().size(), 1UL);
+
+ // Second frame, |GetCurrentCanvases| should be empty at the start
+ flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300));
+ XCTAssertTrue(flutterPlatformViewsController->GetCurrentCanvases().empty());
+
+ auto embeddedViewParams2 =
+ std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
+ flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams2));
+ flutterPlatformViewsController->CompositeEmbeddedView(0);
+ XCTAssertEqual(flutterPlatformViewsController->GetCurrentCanvases().size(), 1UL);
+}
+
- (int)alphaOfPoint:(CGPoint)point onView:(UIView*)view {
unsigned char pixel[4] = {0};
diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h
index 4192562..584a201 100644
--- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h
+++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h
@@ -141,7 +141,8 @@
NSString* factoryId,
FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy);
- void SetFrameSize(SkISize frame_size);
+ // Called at the begining of each frame.
+ void BeginFrame(SkISize frame_size);
// Indicates that we don't compisite any platform views or overlays during this frame.
// Also reverts the composition_order_ to its original state at the begining of the frame.
@@ -175,12 +176,6 @@
std::unique_ptr<SurfaceFrame> frame,
const std::shared_ptr<fml::SyncSwitch>& gpu_disable_sync_switch);
- // Invoked at the very end of a frame.
- // After invoking this method, nothing should happen on the current TaskRunner during the same
- // frame.
- void EndFrame(bool should_resubmit_frame,
- fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger);
-
void OnMethodCall(FlutterMethodCall* call, FlutterResult& result);
private:
@@ -309,6 +304,9 @@
std::shared_ptr<IOSContext> ios_context,
std::unique_ptr<SurfaceFrame> frame);
+ // Resets the state of the frame.
+ void ResetFrameState();
+
FML_DISALLOW_COPY_AND_ASSIGN(FlutterPlatformViewsController);
};
diff --git a/shell/platform/darwin/ios/ios_external_view_embedder.mm b/shell/platform/darwin/ios/ios_external_view_embedder.mm
index cfb3424..9ea32d1 100644
--- a/shell/platform/darwin/ios/ios_external_view_embedder.mm
+++ b/shell/platform/darwin/ios/ios_external_view_embedder.mm
@@ -37,7 +37,7 @@
fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
TRACE_EVENT0("flutter", "IOSExternalViewEmbedder::BeginFrame");
FML_CHECK(platform_views_controller_);
- platform_views_controller_->SetFrameSize(frame_size);
+ platform_views_controller_->BeginFrame(frame_size);
}
// |ExternalViewEmbedder|
@@ -88,7 +88,6 @@
fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
TRACE_EVENT0("flutter", "IOSExternalViewEmbedder::EndFrame");
FML_CHECK(platform_views_controller_);
- return platform_views_controller_->EndFrame(should_resubmit_frame, raster_thread_merger);
}
// |ExternalViewEmbedder|
diff --git a/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj b/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj
index 98565ef..c52019d 100644
--- a/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj
+++ b/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj
@@ -57,6 +57,7 @@
6816DBAD2318696600A51400 /* golden_platform_view_cliprect_iPhone SE_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 6816DBA82318696600A51400 /* golden_platform_view_cliprect_iPhone SE_simulator.png */; };
6816DBAE2318696600A51400 /* golden_platform_view_cliprrect_iPhone SE_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 6816DBA92318696600A51400 /* golden_platform_view_cliprrect_iPhone SE_simulator.png */; };
68A5B63423EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 68A5B63323EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m */; };
+ 68D4017D2564859300ECD91A /* ContinuousTexture.m in Sources */ = {isa = PBXBuildFile; fileRef = 68D4017C2564859300ECD91A /* ContinuousTexture.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -172,6 +173,8 @@
6816DBA82318696600A51400 /* golden_platform_view_cliprect_iPhone SE_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_cliprect_iPhone SE_simulator.png"; sourceTree = "<group>"; };
6816DBA92318696600A51400 /* golden_platform_view_cliprrect_iPhone SE_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_cliprrect_iPhone SE_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>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -238,6 +241,8 @@
0A57B3BC2323C4BD00DD9521 /* ScreenBeforeFlutter.m */,
0A57B3BE2323C74200DD9521 /* FlutterEngine+ScenariosTest.m */,
0A57B3C02323C74D00DD9521 /* FlutterEngine+ScenariosTest.h */,
+ 68D4017B2564859300ECD91A /* ContinuousTexture.h */,
+ 68D4017C2564859300ECD91A /* ContinuousTexture.m */,
);
path = Scenarios;
sourceTree = "<group>";
@@ -462,6 +467,7 @@
buildActionMask = 2147483647;
files = (
248D76DA22E388380012F0C1 /* main.m in Sources */,
+ 68D4017D2564859300ECD91A /* ContinuousTexture.m in Sources */,
24F1FB89230B4579005ACE7C /* TextPlatformView.m in Sources */,
248D76CC22E388370012F0C1 /* AppDelegate.m in Sources */,
0A57B3BF2323C74200DD9521 /* FlutterEngine+ScenariosTest.m in Sources */,
diff --git a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m
index 96ddd0c..0cbdafa 100644
--- a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m
+++ b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m
@@ -4,6 +4,7 @@
#import "AppDelegate.h"
+#import "ContinuousTexture.h"
#import "FlutterEngine+ScenariosTest.h"
#import "ScreenBeforeFlutter.h"
#import "TextPlatformView.h"
@@ -52,6 +53,7 @@
@"--tap-status-bar" : @"tap_status_bar",
@"--text-semantics-focus" : @"text_semantics_focus",
@"--animated-color-square" : @"animated_color_square",
+ @"--platform-view-with-continuous-texture" : @"platform_view_with_continuous_texture"
};
__block NSString* flutterViewControllerTestName = nil;
[launchArgsMap
@@ -70,6 +72,10 @@
}
[self.window makeKeyAndVisible];
+ if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--with-continuous-texture"]) {
+ [ContinuousTexture
+ registerWithRegistrar:[self registrarForPlugin:@"com.constant.firing.texture"]];
+ }
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
diff --git a/testing/scenario_app/ios/Scenarios/Scenarios/ContinuousTexture.h b/testing/scenario_app/ios/Scenarios/Scenarios/ContinuousTexture.h
new file mode 100644
index 0000000..996de85
--- /dev/null
+++ b/testing/scenario_app/ios/Scenarios/Scenarios/ContinuousTexture.h
@@ -0,0 +1,20 @@
+// Copyright 2019 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 <Flutter/Flutter.h>
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+// A texture plugin that ready textures continuously.
+@interface ContinuousTexture : NSObject <FlutterPlugin>
+
+@end
+
+// The testing texture used by |ContinuousTexture|
+@interface FlutterScenarioTestTexture : NSObject <FlutterTexture>
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/testing/scenario_app/ios/Scenarios/Scenarios/ContinuousTexture.m b/testing/scenario_app/ios/Scenarios/Scenarios/ContinuousTexture.m
new file mode 100644
index 0000000..0211062
--- /dev/null
+++ b/testing/scenario_app/ios/Scenarios/Scenarios/ContinuousTexture.m
@@ -0,0 +1,43 @@
+// Copyright 2019 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 "ContinuousTexture.h"
+
+@implementation ContinuousTexture
+
++ (void)registerWithRegistrar:(nonnull NSObject<FlutterPluginRegistrar>*)registrar {
+ NSObject<FlutterTextureRegistry>* textureRegistry = [registrar textures];
+ FlutterScenarioTestTexture* texture = [[FlutterScenarioTestTexture alloc] init];
+ int64_t textureId = [textureRegistry registerTexture:texture];
+ [NSTimer scheduledTimerWithTimeInterval:0.05
+ repeats:YES
+ block:^(NSTimer* _Nonnull timer) {
+ [textureRegistry textureFrameAvailable:textureId];
+ }];
+}
+
+@end
+
+@implementation FlutterScenarioTestTexture
+
+- (CVPixelBufferRef _Nullable)copyPixelBuffer {
+ return [self pixelBuffer];
+}
+
+- (CVPixelBufferRef)pixelBuffer {
+ NSDictionary* options = @{
+ // This key is required to generate SKPicture with CVPixelBufferRef in metal.
+ (NSString*)kCVPixelBufferMetalCompatibilityKey : @YES
+ };
+ CVPixelBufferRef pxbuffer = NULL;
+ CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, 200, 200, kCVPixelFormatType_32BGRA,
+ (__bridge CFDictionaryRef)options, &pxbuffer);
+
+ NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL);
+
+ CVPixelBufferLockBaseAddress(pxbuffer, 0);
+ return pxbuffer;
+}
+
+@end
diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/PlatformViewUITests.m b/testing/scenario_app/ios/Scenarios/ScenariosUITests/PlatformViewUITests.m
index 1d23e3a..24419a6 100644
--- a/testing/scenario_app/ios/Scenarios/ScenariosUITests/PlatformViewUITests.m
+++ b/testing/scenario_app/ios/Scenarios/ScenariosUITests/PlatformViewUITests.m
@@ -4,6 +4,8 @@
#import "GoldenPlatformViewTests.h"
+static const NSInteger kSecondsToWaitForPlatformView = 30;
+
@interface PlatformViewUITests : GoldenPlatformViewTests
@end
@@ -170,4 +172,35 @@
XCUIDevice.sharedDevice.orientation = UIDeviceOrientationLandscapeLeft;
[self checkGolden];
}
+
+@end
+
+@interface PlatformViewWithContinuousTexture : XCTestCase
+
+@end
+
+@implementation PlatformViewWithContinuousTexture
+
+- (void)setUp {
+ self.continueAfterFailure = NO;
+}
+
+- (void)testPlatformViewWithContinuousTexture {
+ XCUIApplication* app = [[XCUIApplication alloc] init];
+ app.launchArguments =
+ @[ @"--platform-view-with-continuous-texture", @"--with-continuous-texture" ];
+ [app launch];
+
+ XCUIElement* platformView = app.textViews.firstMatch;
+ BOOL exists = [platformView waitForExistenceWithTimeout:kSecondsToWaitForPlatformView];
+ if (!exists) {
+ XCTFail(@"It took longer than %@ second to find the platform view."
+ @"There might be issues with the platform view's construction,"
+ @"or with how the scenario is built.",
+ @(kSecondsToWaitForPlatformView));
+ }
+
+ XCTAssertNotNil(platformView);
+}
+
@end
diff --git a/testing/scenario_app/lib/src/platform_view.dart b/testing/scenario_app/lib/src/platform_view.dart
index fd6f5f4..31f45fd 100644
--- a/testing/scenario_app/lib/src/platform_view.dart
+++ b/testing/scenario_app/lib/src/platform_view.dart
@@ -613,6 +613,23 @@
}
}
+/// A simple platform view for testing platform view with a continuous texture layer.
+/// For example, it simulates a video being played.
+class PlatformViewWithContinuousTexture extends PlatformViewScenario {
+ /// Constructs a platform view with continuous texture layer.
+ PlatformViewWithContinuousTexture(PlatformDispatcher dispatcher, String text, { int id = 0 })
+ : super(dispatcher, text, id: id);
+
+ @override
+ void onBeginFrame(Duration duration) {
+ final SceneBuilder builder = SceneBuilder();
+
+ builder.addTexture(0, width: 300, height: 300, offset: const Offset(200, 200));
+
+ finishBuilderByAddingPlatformViewAndPicture(builder, id);
+ }
+}
+
mixin _BasePlatformViewScenarioMixin on Scenario {
int _textureId;
diff --git a/testing/scenario_app/lib/src/scenarios.dart b/testing/scenario_app/lib/src/scenarios.dart
index 7431752..e2c1f27 100644
--- a/testing/scenario_app/lib/src/scenarios.dart
+++ b/testing/scenario_app/lib/src/scenarios.dart
@@ -43,6 +43,7 @@
'tap_status_bar': () => TouchesScenario(PlatformDispatcher.instance),
'text_semantics_focus': () => SendTextFocusSemantics(PlatformDispatcher.instance),
'initial_route_reply': () => InitialRouteReply(PlatformDispatcher.instance),
+ 'platform_view_with_continuous_texture': () => PlatformViewWithContinuousTexture(PlatformDispatcher.instance, 'Platform View', id: _viewId++),
};
Map<String, dynamic> _currentScenarioParams = <String, dynamic>{};