diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index b8612946af2582d75c504f3177639acb9802f683..f65cb5d5dd7913a831c378c6c2730204f6d538a3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -241,13 +241,13 @@ void FlutterPlatformViewsController::RegisterViewFactory( 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::BringLayersIntoView(LayersMap layer_map) { } } -void FlutterPlatformViewsController::EndFrame( - bool should_resubmit_frame, - fml::RefPtr raster_thread_merger) { - // Reset the composition order, so next frame starts empty. - composition_order_.clear(); -} - std::shared_ptr FlutterPlatformViewsController::GetLayer( GrDirectContext* gr_context, std::shared_ptr ios_context, @@ -705,6 +698,11 @@ void FlutterPlatformViewsController::CommitCATransactionIfNeeded() { } } +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 43b9243fd3c2636c1fff5e03c088abd5ae298306..0b0aa2b80da115d0a82ff22a372581f07b32d41f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -204,8 +204,6 @@ fml::RefPtr CreateNewThread(std::string name) { result); XCTAssertNotNil(gMockPlatformView); - - flutterPlatformViewsController->Reset(); } - (void)testChildClippingViewHitTests { @@ -281,7 +279,6 @@ fml::RefPtr CreateNewThread(std::string name) { CGRect platformViewRectInFlutterView = [gMockPlatformView convertRect:gMockPlatformView.bounds toView:mockFlutterView]; XCTAssertTrue(CGRectEqualToRect(platformViewRectInFlutterView, CGRectMake(100, 100, 300, 300))); - flutterPlatformViewsController->Reset(); } - (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView { @@ -351,8 +348,6 @@ fml::RefPtr CreateNewThread(std::string name) { XCTAssertLessThan( fabs(platformViewRectInFlutterView.size.height - childClippingView.frame.size.height), kFloatCompareEpsilon); - - flutterPlatformViewsController->Reset(); } - (void)testClipRect { @@ -424,7 +419,6 @@ fml::RefPtr CreateNewThread(std::string name) { } } } - flutterPlatformViewsController->Reset(); } - (void)testClipRRect { @@ -496,7 +490,6 @@ fml::RefPtr CreateNewThread(std::string name) { } } } - flutterPlatformViewsController->Reset(); } - (void)testClipPath { @@ -569,7 +562,6 @@ fml::RefPtr CreateNewThread(std::string name) { } } } - flutterPlatformViewsController->Reset(); } - (void)testSetFlutterViewControllerAfterCreateCanStillDispatchTouchEvents { @@ -632,8 +624,6 @@ fml::RefPtr CreateNewThread(std::string name) { flutterPlatformViewsController->SetFlutterViewController(mockFlutterViewContoller); [forwardGectureRecognizer touchesBegan:touches2 withEvent:event2]; OCMVerify([mockFlutterViewContoller touchesBegan:touches2 withEvent:event2]); - - flutterPlatformViewsController->Reset(); } - (void)testSetFlutterViewControllerInTheMiddleOfTouchEventShouldStillAllowGesturesToBeHandled { @@ -884,8 +874,6 @@ fml::RefPtr CreateNewThread(std::string name) { gpu_is_disabled->SetSwitch(false); XCTAssertTrue(flutterPlatformViewsController->SubmitFrame( nullptr, nullptr, std::move(mock_surface_submit_false), gpu_is_disabled)); - - flutterPlatformViewsController->Reset(); } - (void) @@ -937,6 +925,59 @@ fml::RefPtr CreateNewThread(std::string name) { 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(); + auto platform_view = std::make_unique( + /*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(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(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 7a49334410b738e9f31418d6acbb8b557ddc8620..3debeab09b444f143c8777761801ec9a6e666e95 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 @@ class FlutterPlatformViewsController { 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 @@ class FlutterPlatformViewsController { std::unique_ptr frame, const std::shared_ptr& 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 raster_thread_merger); - void OnMethodCall(FlutterMethodCall* call, FlutterResult& result); private: @@ -309,6 +304,9 @@ class FlutterPlatformViewsController { std::shared_ptr ios_context, std::unique_ptr 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 cfb34248dba38c0e48f8d8cd6ea7e23f6face8d7..9ea32d1b97fd5cc1f60ee24e09bba6a20c73836b 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 @@ void IOSExternalViewEmbedder::BeginFrame( fml::RefPtr 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 @@ void IOSExternalViewEmbedder::EndFrame(bool should_resubmit_frame, fml::RefPtr 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 98565ef094b0d534356ac4084fae409dd9ce2d76..c52019df01176c7c8fa68f45a1c15db0d063a473 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 = ""; }; 6816DBA92318696600A51400 /* golden_platform_view_cliprrect_iPhone SE_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_cliprrect_iPhone SE_simulator.png"; sourceTree = ""; }; 68A5B63323EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PlatformViewGestureRecognizerTests.m; sourceTree = ""; }; + 68D4017B2564859300ECD91A /* ContinuousTexture.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ContinuousTexture.h; sourceTree = ""; }; + 68D4017C2564859300ECD91A /* ContinuousTexture.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ContinuousTexture.m; sourceTree = ""; }; /* 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 = ""; @@ -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 96ddd0c734f6d1c6c4e263cbd55ce2260a7ea7b5..0cbdafaa5984f4b47ab8237fa3cf4138c334afde 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 0000000000000000000000000000000000000000..996de850caa099ef14a98f1ab5bb2a4cc4b46934 --- /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 +#import + +NS_ASSUME_NONNULL_BEGIN + +// A texture plugin that ready textures continuously. +@interface ContinuousTexture : NSObject + +@end + +// The testing texture used by |ContinuousTexture| +@interface FlutterScenarioTestTexture : NSObject + +@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 0000000000000000000000000000000000000000..0211062a283b5590614c66dbbd4c7ff45bd6b301 --- /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*)registrar { + NSObject* 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 1d23e3a0643780273f2d77e156fc41ee0f2c0119..24419a649910bc777a9f8b6f1bae96d98d5218a4 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 8fffe8c21bb388b42502ce933e17a5dcd322b6f2..6bdcf8f5454360e2b7507004da749260321ead7b 100644 --- a/testing/scenario_app/lib/src/platform_view.dart +++ b/testing/scenario_app/lib/src/platform_view.dart @@ -579,6 +579,23 @@ class PlatformViewForTouchIOSScenario extends Scenario } } +/// 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 7431752d4bbe12632ecda212456f2af730e2a5de..e2c1f2720654617d8343d3b289789d6ec9103472 100644 --- a/testing/scenario_app/lib/src/scenarios.dart +++ b/testing/scenario_app/lib/src/scenarios.dart @@ -43,6 +43,7 @@ Map _scenarios = { '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 _currentScenarioParams = {};