From 5d69b4d07b14b6972a8adcb25ae7ec2136967302 Mon Sep 17 00:00:00 2001 From: jonahwilliams Date: Sat, 3 Aug 2024 11:18:08 -0700 Subject: [PATCH 1/6] [iOS] clean ups to platform view controller --- shell/platform/darwin/ios/BUILD.gn | 3 +- .../ios/framework/Source/FlutterEngine.mm | 6 +- .../framework/Source/FlutterEngine_Internal.h | 2 +- .../Source/FlutterPlatformViewsTest.mm | 70 +-- .../Source/FlutterPlatformViews_Internal.h | 360 +------------ .../Source/FlutterPlatformViews_Internal.mm | 332 ++++++++++-- .../darwin/ios/framework/Source/FlutterView.h | 2 +- .../framework/Source/FlutterViewController.mm | 2 +- .../Source/FlutterViewController_Internal.h | 4 +- .../ios/framework/Source/FlutterViewTest.mm | 6 +- .../Source/SemanticsObjectTestMocks.h | 4 +- .../framework/Source/accessibility_bridge.h | 6 +- .../framework/Source/accessibility_bridge.mm | 2 +- .../Source/accessibility_bridge_ios.h | 4 +- .../Source/accessibility_bridge_test.mm | 15 +- .../Source/platform_views_controller.h | 402 ++++++++++++++ ...mViews.mm => platform_views_controller.mm} | 510 +++++------------- .../darwin/ios/ios_external_view_embedder.h | 9 +- .../darwin/ios/ios_external_view_embedder.mm | 2 +- shell/platform/darwin/ios/platform_view_ios.h | 6 +- .../platform/darwin/ios/platform_view_ios.mm | 4 +- 21 files changed, 905 insertions(+), 846 deletions(-) create mode 100644 shell/platform/darwin/ios/framework/Source/platform_views_controller.h rename shell/platform/darwin/ios/framework/Source/{FlutterPlatformViews.mm => platform_views_controller.mm} (66%) diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index f3e3389a3df2f..85ff9cd859100 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -77,7 +77,6 @@ source_set("flutter_framework_source_arc") { "framework/Source/FlutterMetalLayer.mm", "framework/Source/FlutterOverlayView.h", "framework/Source/FlutterOverlayView.mm", - "framework/Source/FlutterPlatformViews.mm", "framework/Source/FlutterPlatformViews_Internal.h", "framework/Source/FlutterPlatformViews_Internal.mm", "framework/Source/FlutterPluginAppLifeCycleDelegate.mm", @@ -112,6 +111,8 @@ source_set("flutter_framework_source_arc") { "framework/Source/connection_collection.mm", "framework/Source/platform_message_response_darwin.h", "framework/Source/platform_message_response_darwin.mm", + "framework/Source/platform_views_controller.h", + "framework/Source/platform_views_controller.mm", "framework/Source/profiler_metrics_ios.h", "framework/Source/profiler_metrics_ios.mm", "framework/Source/vsync_waiter_ios.h", diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 669eb9a35a39f..71a0372048774 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -123,7 +123,7 @@ @implementation FlutterEngine { fml::WeakNSObject _viewController; fml::scoped_nsobject _publisher; - std::shared_ptr _platformViewsController; + std::shared_ptr _platformViewsController; flutter::IOSRenderingAPI _renderingApi; std::shared_ptr _profiler_metrics; std::shared_ptr _profiler; @@ -271,7 +271,7 @@ - (void)setUpApplicationLifecycleNotifications:(NSNotificationCenter*)center { - (void)recreatePlatformViewController { _renderingApi = flutter::GetRenderingAPIForProcess(FlutterView.forceSoftwareRendering); - _platformViewsController.reset(new flutter::FlutterPlatformViewsController()); + _platformViewsController.reset(new flutter::PlatformViewsController()); } - (flutter::IOSRenderingAPI)platformViewsRenderingAPI { @@ -495,7 +495,7 @@ - (FlutterViewController*)viewController { - (FlutterPlatformPlugin*)platformPlugin { return _platformPlugin.get(); } -- (std::shared_ptr&)platformViewsController { +- (std::shared_ptr&)platformViewsController { return _platformViewsController; } - (FlutterTextInputPlugin*)textInputPlugin { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h index 03c1e6ef34d7e..489dfd38c4d87 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h @@ -48,7 +48,7 @@ extern NSString* const kFlutterEngineWillDealloc; base64Encode:(bool)base64Encode; - (FlutterPlatformPlugin*)platformPlugin; -- (std::shared_ptr&)platformViewsController; +- (std::shared_ptr&)platformViewsController; - (FlutterTextInputPlugin*)textInputPlugin; - (FlutterRestorationPlugin*)restorationPlugin; - (void)launchEngine:(nullable NSString*)entrypoint diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index 16e51ff0cd1d0..7bfc93904ac02 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -6,6 +6,7 @@ #import #import #include "fml/synchronization/count_down_latch.h" +#include "shell/platform/darwin/ios/framework/Source/platform_views_controller.h" #import "flutter/fml/thread.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" @@ -121,6 +122,11 @@ void UpdateAssetResolverByType(std::unique_ptr updated_a flutter::Settings settings_; }; +BOOL BlurRadiusEqualToBlurRadius(CGFloat radius1, CGFloat radius2) { + const CGFloat epsilon = 0.01; + return radius1 - radius2 < epsilon; +} + } // namespace } // namespace flutter @@ -144,7 +150,7 @@ - (void)testFlutterViewOnlyCreateOnceInOneFrame { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -200,7 +206,7 @@ - (void)testCanCreatePlatformViewWithoutFlutterView { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -293,7 +299,7 @@ - (void)testApplyBackdropFilter { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -370,7 +376,7 @@ - (void)testApplyBackdropFilterWithCorrectFrame { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -447,7 +453,7 @@ - (void)testApplyMultipleBackdropFilters { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -525,7 +531,7 @@ - (void)testAddBackdropFilters { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -646,7 +652,7 @@ - (void)testRemoveBackdropFilters { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -792,7 +798,7 @@ - (void)testEditBackdropFilters { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -1082,7 +1088,7 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -1401,7 +1407,7 @@ - (void)testCompositePlatformView { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -1462,7 +1468,7 @@ - (void)testBackdropFilterCorrectlyPushedAndReset { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -1563,7 +1569,7 @@ - (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -1639,7 +1645,7 @@ - (void)testClipsDoNotInterceptWithPlatformViewShouldNotAddMaskView { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -1711,7 +1717,7 @@ - (void)testClipRRectOnlyHasCornersInterceptWithPlatformViewShouldAddMaskView { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -1782,7 +1788,7 @@ - (void)testClipRect { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -1858,7 +1864,7 @@ - (void)testClipRRect { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -1961,7 +1967,7 @@ - (void)testClipPath { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -2065,7 +2071,7 @@ - (void)testSetFlutterViewControllerAfterCreateCanStillDispatchTouchEvents { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -2132,7 +2138,7 @@ - (void)testSetFlutterViewControllerInTheMiddleOfTouchEventShouldStillAllowGestu /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -2256,7 +2262,7 @@ - (void)testSetFlutterViewControllerInTheMiddleOfTouchEventShouldStillAllowGestu /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -2370,7 +2376,7 @@ - (void)testFlutterPlatformViewTouchesCancelledEventAreForcedToBeCancelled { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -2435,7 +2441,7 @@ - (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashin /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -2506,7 +2512,7 @@ - (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashin /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -2559,7 +2565,7 @@ - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -2622,7 +2628,7 @@ - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -2724,7 +2730,7 @@ - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -2911,7 +2917,7 @@ - (void)testClipMaskViewIsReused { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -3002,7 +3008,7 @@ - (void)testDifferentClipMaskViewIsUsedForEachView { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -3082,7 +3088,7 @@ - (void)testMaskViewUsesCAShapeLayerAsTheBackingLayer { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -3175,7 +3181,7 @@ - (void)testDisposingViewInCompositionOrderDoNotCrash { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -3286,7 +3292,7 @@ - (void)testOnlyPlatformViewsAreRemovedWhenReset { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -3364,7 +3370,7 @@ - (void)testLayerPool { auto ios_context = [engine iosPlatformView]->GetIosContext(); auto gr_context = ios_context->GetMainContext(); - auto pool = flutter::FlutterPlatformViewLayerPool{}; + auto pool = flutter::OverlayLayerPool{}; // Add layers to the pool. pool.CreateLayer(gr_context.get(), ios_context, MTLPixelFormatBGRA8Unorm); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index e87d1cbb15cfb..b42c267074d4f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -19,6 +19,7 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/platform_views_controller.h" #import "flutter/shell/platform/darwin/ios/ios_context.h" @class FlutterTouchInterceptingView; @@ -129,363 +130,6 @@ - (NSMutableArray*)backdropFilterSubviews; @end -namespace flutter { -// Converts a SkMatrix to CATransform3D. -// Certain fields are ignored in CATransform3D since SkMatrix is 3x3 and CATransform3D is 4x4. -CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix& matrix); - -// Reset the anchor of `layer` to match the transform operation from flow. -// The position of the `layer` should be unchanged after resetting the anchor. -void ResetAnchor(CALayer* layer); - -CGRect GetCGRectFromSkRect(const SkRect& clipSkRect); -BOOL BlurRadiusEqualToBlurRadius(CGFloat radius1, CGFloat radius2); - -class IOSSurface; - -struct FlutterPlatformViewLayer { - FlutterPlatformViewLayer(const fml::scoped_nsobject& overlay_view, - const fml::scoped_nsobject& overlay_view_wrapper, - std::unique_ptr ios_surface, - std::unique_ptr surface); - - ~FlutterPlatformViewLayer(); - - fml::scoped_nsobject overlay_view; - fml::scoped_nsobject overlay_view_wrapper; - std::unique_ptr ios_surface; - std::unique_ptr surface; - - // Whether a frame for this layer was submitted. - bool did_submit_last_frame; - - // The GrContext that is currently used by the overlay surfaces. - // We track this to know when the GrContext for the Flutter app has changed - // so we can update the overlay with the new context. - GrDirectContext* gr_context; - - void UpdateViewState(UIView* flutter_view, SkRect rect, int64_t view_id, int64_t overlay_id); -}; - -/// @brief Storage for Overlay layers across frames. -/// -/// Note: this class does not synchronize access to its layers or any layer removal. As it -/// is currently used, layers must be created on the platform thread but other methods of -/// it are called on the raster thread. This is safe as overlay layers are only ever added -/// while the raster thread is latched. -class FlutterPlatformViewLayerPool { - public: - FlutterPlatformViewLayerPool() = default; - - ~FlutterPlatformViewLayerPool() = default; - - /// @brief Gets a layer from the pool if available. - /// - /// The layer is marked as used until [RecycleLayers] is called. - std::shared_ptr GetNextLayer(); - - /// @brief Create a new overlay layer. - /// - /// This method can only be called on the Platform thread. - void CreateLayer(GrDirectContext* gr_context, - const std::shared_ptr& ios_context, - MTLPixelFormat pixel_format); - - /// @brief Removes unused layers from the pool. Returns the unused layers. - std::vector> RemoveUnusedLayers(); - - /// @brief Marks the layers in the pool as available for reuse. - void RecycleLayers(); - - /// @brief The count of layers currently in the pool. - size_t size() const; - - private: - // The index of the entry in the layers_ vector that determines the beginning of the unused - // layers. For example, consider the following vector: - // _____ - // | 0 | - /// |---| - /// | 1 | <-- available_layer_index_ - /// |---| - /// | 2 | - /// |---| - /// - /// This indicates that entries starting from 1 can be reused meanwhile the entry at position 0 - /// cannot be reused. - size_t available_layer_index_ = 0; - std::vector> layers_; - - FML_DISALLOW_COPY_AND_ASSIGN(FlutterPlatformViewLayerPool); -}; - -class FlutterPlatformViewsController { - public: - FlutterPlatformViewsController(); - - ~FlutterPlatformViewsController(); - - fml::WeakPtr GetWeakPtr(); - - void SetTaskRunner(const fml::RefPtr& platform_task_runner); - - void SetFlutterView(UIView* flutter_view) __attribute__((cf_audited_transfer)); - - void SetFlutterViewController(UIViewController* flutter_view_controller) - __attribute__((cf_audited_transfer)); - - UIViewController* getFlutterViewController() - __attribute__((cf_audited_transfer)); - - void RegisterViewFactory( - NSObject* factory, - NSString* factoryId, - FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy) - __attribute__((cf_audited_transfer)); - - // Called at the beginning 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 beginning of the frame. - void CancelFrame(); - - // Runs on the raster thread. - void PrerollCompositeEmbeddedView(int64_t view_id, - std::unique_ptr params); - - size_t EmbeddedViewCount() const; - - size_t LayerPoolSize() const; - - // Returns the `FlutterPlatformView`'s `view` object associated with the view_id. - // - // If the `FlutterPlatformViewsController` does not contain any `FlutterPlatformView` object or - // a `FlutterPlatformView` object associated with the view_id cannot be found, the method - // returns nil. - UIView* GetPlatformViewByID(int64_t view_id); - - // Returns the `FlutterTouchInterceptingView` with the view_id. - // - // If the `FlutterPlatformViewsController` does not contain any `FlutterPlatformView` object or - // a `FlutterPlatformView` object associated with the view_id cannot be found, the method - // returns nil. - FlutterTouchInterceptingView* GetFlutterTouchInterceptingViewByID(int64_t view_id); - - // Runs on the raster thread. - PostPrerollResult PostPrerollAction( - const fml::RefPtr& raster_thread_merger); - - // Runs on the raster thread. - void EndFrame(bool should_resubmit_frame, - const fml::RefPtr& raster_thread_merger); - - // Return the Canvas for the overlay slice for the given platform view. - // - // Runs on the raster thread. - DlCanvas* CompositeEmbeddedView(int64_t view_id); - - // Discards all platform views instances and auxiliary resources. - // - // Runs on the raster thread. - void Reset(); - - // Encode rendering for the Flutter overlay views and queue up perform platform view mutations. - // - // Runs on the raster thread. - bool SubmitFrame(GrDirectContext* gr_context, - const std::shared_ptr& ios_context, - std::unique_ptr frame); - - void OnMethodCall(FlutterMethodCall* call, FlutterResult result) - __attribute__((cf_audited_transfer)); - - // Returns the platform view id if the platform view (or any of its descendant view) is the first - // responder. Returns -1 if no such platform view is found. - long FindFirstResponderPlatformViewId(); - - // Pushes backdrop filter mutation to the mutator stack of each visited platform view. - void PushFilterToVisitedPlatformViews(const std::shared_ptr& filter, - const SkRect& filter_rect); - - // Pushes the view id of a visted platform view to the list of visied platform views. - void PushVisitedPlatformView(int64_t view_id) { visited_platform_views_.push_back(view_id); } - - // Visible for testing. - void CompositeWithParams(int64_t view_id, const EmbeddedViewParams& params); - - const EmbeddedViewParams& GetCompositionParams(int64_t view_id) const { - return current_composition_params_.find(view_id)->second; - } - - private: - struct LayerData { - SkRect rect; - int64_t view_id; - int64_t overlay_id; - std::shared_ptr layer; - }; - - using LayersMap = std::unordered_map; - - // Update the buffers and mutate the platform views in CATransaction. - // - // Runs on the platform thread. - void PerformSubmit(const LayersMap& platform_view_layers, - std::unordered_map& current_composition_params, - const std::unordered_set& views_to_recomposite, - const std::vector& composition_order, - const std::vector>& unused_layers, - const std::vector>& surface_frames); - - /// @brief Populate any missing overlay layers. - /// - /// This requires posting a task to the platform thread and blocking on its completion. - void CreateMissingOverlays(GrDirectContext* gr_context, - const std::shared_ptr& ios_context, - size_t required_overlay_layers); - - void OnCreate(FlutterMethodCall* call, FlutterResult result) __attribute__((cf_audited_transfer)); - void OnDispose(FlutterMethodCall* call, FlutterResult result) - __attribute__((cf_audited_transfer)); - void OnAcceptGesture(FlutterMethodCall* call, FlutterResult result) - __attribute__((cf_audited_transfer)); - void OnRejectGesture(FlutterMethodCall* call, FlutterResult result) - __attribute__((cf_audited_transfer)); - - /// @brief Return all views to be disposed on the platform thread. - std::vector GetViewsToDispose(); - - void ClipViewSetMaskView(UIView* clipView) __attribute__((cf_audited_transfer)); - - // Applies the mutators in the mutators_stack to the UIView chain that was constructed by - // `ReconstructClipViewsChain` - // - // 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 - // rect of the PlatformView, the clip mutator is not applied for performance optimization. - void ApplyMutators(const MutatorsStack& mutators_stack, - UIView* embedded_view, - const SkRect& bounding_rect) __attribute__((cf_audited_transfer)); - - std::shared_ptr GetExistingLayer(); - - // Runs on the platform thread. - void CreateLayer(GrDirectContext* gr_context, - const std::shared_ptr& ios_context, - MTLPixelFormat pixel_format); - - // Removes overlay views and platform views that aren't needed in the current frame. - // Must run on the platform thread. - void RemoveUnusedLayers( - const std::vector>& unused_layers, - const std::vector& composition_order); - - // Appends the overlay views and platform view and sets their z index based on the composition - // order. - void BringLayersIntoView(const LayersMap& layer_map, - const std::vector& composition_order); - - // Resets the state of the frame. - void ResetFrameState(); - - // The pool of reusable view layers. The pool allows to recycle layer in each frame. - std::unique_ptr layer_pool_; - - // The platform view's |EmbedderViewSlice| keyed off the view id, which contains any subsequent - // operation until the next platform view or the end of the last leaf node in the layer tree. - // - // The Slices are deleted by the FlutterPlatformViewsController.reset(). - std::unordered_map> slices_; - - fml::scoped_nsobject channel_; - fml::scoped_nsobject flutter_view_; - fml::scoped_nsobject> flutter_view_controller_; - fml::scoped_nsobject mask_view_pool_; - std::unordered_map>> - factories_; - - // The FlutterPlatformViewGestureRecognizersBlockingPolicy for each type of platform view. - std::unordered_map - gesture_recognizers_blocking_policies_; - - /// The size of the current onscreen surface in physical pixels. - SkISize frame_size_; - - /// The task runner for posting tasks to the platform thread. - fml::RefPtr platform_task_runner_; - - /// Each of the following structs stores part of the platform view hierarchy according to its - /// ID. - /// - /// This data must only be accessed on the platform thread. - struct PlatformViewData { - fml::scoped_nsobject> view; - fml::scoped_nsobject touch_interceptor; - fml::scoped_nsobject root_view; - }; - - /// This data must only be accessed on the platform thread. - std::unordered_map platform_views_; - - /// The composition parameters for each platform view. - /// - /// This state is only modified on the raster thread. - std::unordered_map current_composition_params_; - - /// Method channel `OnDispose` calls adds the views to be disposed to this set to be disposed on - /// the next frame. - /// - /// This state is modified on both the platform and raster thread. - std::unordered_set views_to_dispose_; - - /// view IDs in composition order. - /// - /// This state is only modified on the raster thread. - std::vector composition_order_; - - /// platform view IDs visited during layer tree composition. - /// - /// This state is only modified on the raster thread. - std::vector visited_platform_views_; - - /// Only composite platform views in this set. - /// - /// This state is only modified on the raster thread. - std::unordered_set views_to_recomposite_; - -#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG - /// A set to keep track of embedded views that do not have (0, 0) origin. - /// An insertion triggers a warning message about non-zero origin logged on the debug console. - /// See https://github.com/flutter/flutter/issues/109700 for details. - std::unordered_set non_zero_origin_views_; -#endif - - /// @brief The composition order from the previous thread. - /// - /// Only accessed from the platform thread. - std::vector previous_composition_order_; - - /// Whether the previous frame had any platform views in active composition order. - /// - /// This state is tracked so that the first frame after removing the last platform view - /// runs through the platform view rendering code path, giving us a chance to remove the - /// platform view from the UIView hierarchy. - /// - /// Only accessed from the raster thread. - bool had_platform_views_ = false; - - // WeakPtrFactory must be the last member. - std::unique_ptr> weak_factory_; - - FML_DISALLOW_COPY_AND_ASSIGN(FlutterPlatformViewsController); -}; - -} // namespace flutter - // A UIView that is used as the parent for embedded UIViews. // // This view has 2 roles: @@ -494,7 +138,7 @@ class FlutterPlatformViewsController { @interface FlutterTouchInterceptingView : UIView - (instancetype)initWithEmbeddedView:(UIView*)embeddedView platformViewsController: - (fml::WeakPtr)platformViewsController + (fml::WeakPtr)platformViewsController gestureRecognizersBlockingPolicy: (FlutterPlatformViewGestureRecognizersBlockingPolicy)blockingPolicy; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm index 83d01ec2c6901..f9872be461b28 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm @@ -11,38 +11,11 @@ FLUTTER_ASSERT_ARC static constexpr int kMaxPointsInVerb = 4; -static constexpr NSUInteger kFlutterClippingMaskViewPoolCapacity = 5; -namespace flutter { - -FlutterPlatformViewLayer::FlutterPlatformViewLayer( - const fml::scoped_nsobject& overlay_view, - const fml::scoped_nsobject& overlay_view_wrapper, - std::unique_ptr ios_surface, - std::unique_ptr surface) - : overlay_view(overlay_view), - overlay_view_wrapper(overlay_view_wrapper), - ios_surface(std::move(ios_surface)), - surface(std::move(surface)){}; - -FlutterPlatformViewLayer::~FlutterPlatformViewLayer() = default; - -FlutterPlatformViewsController::FlutterPlatformViewsController() - : layer_pool_(std::make_unique()), - weak_factory_(std::make_unique>(this)) { - mask_view_pool_.reset( - [[FlutterClippingMaskViewPool alloc] initWithCapacity:kFlutterClippingMaskViewPoolCapacity]); -}; - -FlutterPlatformViewsController::~FlutterPlatformViewsController() = default; - -void FlutterPlatformViewsController::SetTaskRunner( - const fml::RefPtr& platform_task_runner) { - platform_task_runner_ = platform_task_runner; -} - -fml::WeakPtr FlutterPlatformViewsController::GetWeakPtr() { - return weak_factory_->GetWeakPtr(); +namespace { +CGRect GetCGRectFromSkRect(const SkRect& clipSkRect) { + return CGRectMake(clipSkRect.fLeft, clipSkRect.fTop, clipSkRect.fRight - clipSkRect.fLeft, + clipSkRect.fBottom - clipSkRect.fTop); } CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix& matrix) { @@ -59,24 +32,7 @@ CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix& matrix) { transform.m24 = matrix.getPerspY(); return transform; } - -void ResetAnchor(CALayer* layer) { - // Flow uses (0, 0) to apply transform matrix so we need to match that in Quartz. - layer.anchorPoint = CGPointZero; - layer.position = CGPointZero; -} - -CGRect GetCGRectFromSkRect(const SkRect& clipSkRect) { - return CGRectMake(clipSkRect.fLeft, clipSkRect.fTop, clipSkRect.fRight - clipSkRect.fLeft, - clipSkRect.fBottom - clipSkRect.fTop); -} - -BOOL BlurRadiusEqualToBlurRadius(CGFloat radius1, CGFloat radius2) { - const CGFloat epsilon = 0.01; - return radius1 - radius2 < epsilon; -} - -} // namespace flutter +} // namespace @interface PlatformViewFilter () @@ -291,11 +247,11 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event { } - (void)clipRect:(const SkRect&)clipSkRect matrix:(const SkMatrix&)matrix { - CGRect clipRect = flutter::GetCGRectFromSkRect(clipSkRect); + CGRect clipRect = GetCGRectFromSkRect(clipSkRect); CGPathRef path = CGPathCreateWithRect(clipRect, nil); // The `matrix` is based on the physical pixels, convert it to UIKit points. CATransform3D matrixInPoints = - CATransform3DConcat(flutter::GetCATransform3DFromSkMatrix(matrix), _reverseScreenScale); + CATransform3DConcat(GetCATransform3DFromSkMatrix(matrix), _reverseScreenScale); [self addTransformedPath:path matrix:matrixInPoints]; } @@ -311,7 +267,7 @@ - (void)clipRRect:(const SkRRect&)clipSkRRect matrix:(const SkMatrix&)matrix { } case SkRRect::kOval_Type: case SkRRect::kSimple_Type: { - CGRect clipRect = flutter::GetCGRectFromSkRect(clipSkRRect.rect()); + CGRect clipRect = GetCGRectFromSkRect(clipSkRRect.rect()); pathRef = CGPathCreateWithRoundedRect(clipRect, clipSkRRect.getSimpleRadii().x(), clipSkRRect.getSimpleRadii().y(), nil); break; @@ -361,7 +317,7 @@ - (void)clipRRect:(const SkRRect&)clipSkRRect matrix:(const SkMatrix&)matrix { } // The `matrix` is based on the physical pixels, convert it to UIKit points. CATransform3D matrixInPoints = - CATransform3DConcat(flutter::GetCATransform3DFromSkMatrix(matrix), _reverseScreenScale); + CATransform3DConcat(GetCATransform3DFromSkMatrix(matrix), _reverseScreenScale); // TODO(cyanglaz): iOS does not seem to support hard edge on CAShapeLayer. It clearly stated that // the CAShaperLayer will be drawn antialiased. Need to figure out a way to do the hard edge // clipping on iOS. @@ -429,7 +385,7 @@ - (void)clipPath:(const SkPath&)path matrix:(const SkMatrix&)matrix { } // The `matrix` is based on the physical pixels, convert it to UIKit points. CATransform3D matrixInPoints = - CATransform3DConcat(flutter::GetCATransform3DFromSkMatrix(matrix), _reverseScreenScale); + CATransform3DConcat(GetCATransform3DFromSkMatrix(matrix), _reverseScreenScale); [self addTransformedPath:pathRef matrix:matrixInPoints]; } @@ -491,3 +447,271 @@ - (void)insertViewToPoolIfNeeded:(FlutterClippingMaskView*)maskView { } @end + +@implementation UIView (FirstResponder) +- (BOOL)flt_hasFirstResponderInViewHierarchySubtree { + if (self.isFirstResponder) { + return YES; + } + for (UIView* subview in self.subviews) { + if (subview.flt_hasFirstResponderInViewHierarchySubtree) { + return YES; + } + } + return NO; +} +@end + +// This recognizers delays touch events from being dispatched to the responder chain until it failed +// recognizing a gesture. +// +// We only fail this recognizer when asked to do so by the Flutter framework (which does so by +// invoking an acceptGesture method on the platform_views channel). And this is how we allow the +// Flutter framework to delay or prevent the embedded view from getting a touch sequence. +@interface DelayingGestureRecognizer : UIGestureRecognizer + +// Indicates that if the `DelayingGestureRecognizer`'s state should be set to +// `UIGestureRecognizerStateEnded` during next `touchesEnded` call. +@property(nonatomic) BOOL shouldEndInNextTouchesEnded; + +// Indicates that the `DelayingGestureRecognizer`'s `touchesEnded` has been invoked without +// setting the state to `UIGestureRecognizerStateEnded`. +@property(nonatomic) BOOL touchedEndedWithoutBlocking; + +@property(nonatomic, readonly) UIGestureRecognizer* forwardingRecognizer; + +- (instancetype)initWithTarget:(id)target + action:(SEL)action + forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer; +@end + +// While the DelayingGestureRecognizer is preventing touches from hitting the responder chain +// the touch events are not arriving to the FlutterView (and thus not arriving to the Flutter +// framework). We use this gesture recognizer to dispatch the events directly to the FlutterView +// while during this phase. +// +// If the Flutter framework decides to dispatch events to the embedded view, we fail the +// DelayingGestureRecognizer which sends the events up the responder chain. But since the events +// are handled by the embedded view they are not delivered to the Flutter framework in this phase +// as well. So during this phase as well the ForwardingGestureRecognizer dispatched the events +// directly to the FlutterView. +@interface ForwardingGestureRecognizer : UIGestureRecognizer +- (instancetype)initWithTarget:(id)target + platformViewsController: + (fml::WeakPtr)platformViewsController; +@end + +@interface FlutterTouchInterceptingView () +@property(nonatomic, weak, readonly) UIView* embeddedView; +@property(nonatomic, readonly) DelayingGestureRecognizer* delayingRecognizer; +@property(nonatomic, readonly) FlutterPlatformViewGestureRecognizersBlockingPolicy blockingPolicy; +@end + +@implementation FlutterTouchInterceptingView +- (instancetype)initWithEmbeddedView:(UIView*)embeddedView + platformViewsController: + (fml::WeakPtr)platformViewsController + gestureRecognizersBlockingPolicy: + (FlutterPlatformViewGestureRecognizersBlockingPolicy)blockingPolicy { + self = [super initWithFrame:embeddedView.frame]; + if (self) { + self.multipleTouchEnabled = YES; + _embeddedView = embeddedView; + embeddedView.autoresizingMask = + (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); + + [self addSubview:embeddedView]; + + ForwardingGestureRecognizer* forwardingRecognizer = + [[ForwardingGestureRecognizer alloc] initWithTarget:self + platformViewsController:platformViewsController]; + + _delayingRecognizer = [[DelayingGestureRecognizer alloc] initWithTarget:self + action:nil + forwardingRecognizer:forwardingRecognizer]; + _blockingPolicy = blockingPolicy; + + [self addGestureRecognizer:_delayingRecognizer]; + [self addGestureRecognizer:forwardingRecognizer]; + } + return self; +} + +- (void)releaseGesture { + self.delayingRecognizer.state = UIGestureRecognizerStateFailed; +} + +- (void)blockGesture { + switch (_blockingPolicy) { + case FlutterPlatformViewGestureRecognizersBlockingPolicyEager: + // We block all other gesture recognizers immediately in this policy. + self.delayingRecognizer.state = UIGestureRecognizerStateEnded; + break; + case FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded: + if (self.delayingRecognizer.touchedEndedWithoutBlocking) { + // If touchesEnded of the `DelayingGesureRecognizer` has been already invoked, + // we want to set the state of the `DelayingGesureRecognizer` to + // `UIGestureRecognizerStateEnded` as soon as possible. + self.delayingRecognizer.state = UIGestureRecognizerStateEnded; + } else { + // If touchesEnded of the `DelayingGesureRecognizer` has not been invoked, + // We will set a flag to notify the `DelayingGesureRecognizer` to set the state to + // `UIGestureRecognizerStateEnded` when touchesEnded is called. + self.delayingRecognizer.shouldEndInNextTouchesEnded = YES; + } + break; + default: + break; + } +} + +// We want the intercepting view to consume the touches and not pass the touches up to the parent +// view. Make the touch event method not call super will not pass the touches up to the parent view. +// Hence we overide the touch event methods and do nothing. +- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { +} + +- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { +} + +- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { +} + +- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { +} + +- (id)accessibilityContainer { + return self.flutterAccessibilityContainer; +} + +@end + +@implementation DelayingGestureRecognizer + +- (instancetype)initWithTarget:(id)target + action:(SEL)action + forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer { + self = [super initWithTarget:target action:action]; + if (self) { + self.delaysTouchesBegan = YES; + self.delaysTouchesEnded = YES; + self.delegate = self; + _shouldEndInNextTouchesEnded = NO; + _touchedEndedWithoutBlocking = NO; + _forwardingRecognizer = forwardingRecognizer; + } + return self; +} + +- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer + shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer { + // The forwarding gesture recognizer should always get all touch events, so it should not be + // required to fail by any other gesture recognizer. + return otherGestureRecognizer != _forwardingRecognizer && otherGestureRecognizer != self; +} + +- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer + shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer { + return otherGestureRecognizer == self; +} + +- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { + self.touchedEndedWithoutBlocking = NO; + [super touchesBegan:touches withEvent:event]; +} + +- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { + if (self.shouldEndInNextTouchesEnded) { + self.state = UIGestureRecognizerStateEnded; + self.shouldEndInNextTouchesEnded = NO; + } else { + self.touchedEndedWithoutBlocking = YES; + } + [super touchesEnded:touches withEvent:event]; +} + +- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { + self.state = UIGestureRecognizerStateFailed; +} +@end + +@implementation ForwardingGestureRecognizer { + // Weak reference to PlatformViewsController. The PlatformViewsController has + // a reference to the FlutterViewController, where we can dispatch pointer events to. + // + // The lifecycle of PlatformViewsController is bind to FlutterEngine, which should always + // outlives the FlutterViewController. And ForwardingGestureRecognizer is owned by a subview of + // FlutterView, so the ForwardingGestureRecognizer never out lives FlutterViewController. + // Therefore, `_platformViewsController` should never be nullptr. + fml::WeakPtr _platformViewsController; + // Counting the pointers that has started in one touch sequence. + NSInteger _currentTouchPointersCount; + // We can't dispatch events to the framework without this back pointer. + // This gesture recognizer retains the `FlutterViewController` until the + // end of a gesture sequence, that is all the touches in touchesBegan are concluded + // with |touchesCancelled| or |touchesEnded|. + fml::scoped_nsobject> _flutterViewController; +} + +- (instancetype)initWithTarget:(id)target + platformViewsController: + (fml::WeakPtr)platformViewsController { + self = [super initWithTarget:target action:nil]; + if (self) { + self.delegate = self; + FML_DCHECK(platformViewsController.get() != nullptr); + _platformViewsController = std::move(platformViewsController); + _currentTouchPointersCount = 0; + } + return self; +} + +- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { + FML_DCHECK(_currentTouchPointersCount >= 0); + if (_currentTouchPointersCount == 0) { + // At the start of each gesture sequence, we reset the `_flutterViewController`, + // so that all the touch events in the same sequence are forwarded to the same + // `_flutterViewController`. + _flutterViewController.reset(_platformViewsController->GetFlutterViewController()); + } + [_flutterViewController.get() touchesBegan:touches withEvent:event]; + _currentTouchPointersCount += touches.count; +} + +- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { + [_flutterViewController.get() touchesMoved:touches withEvent:event]; +} + +- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { + [_flutterViewController.get() touchesEnded:touches withEvent:event]; + _currentTouchPointersCount -= touches.count; + // Touches in one touch sequence are sent to the touchesEnded method separately if different + // fingers stop touching the screen at different time. So one touchesEnded method triggering does + // not necessarially mean the touch sequence has ended. We Only set the state to + // UIGestureRecognizerStateFailed when all the touches in the current touch sequence is ended. + if (_currentTouchPointersCount == 0) { + self.state = UIGestureRecognizerStateFailed; + _flutterViewController.reset(nil); + } +} + +- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { + // In the event of platform view is removed, iOS generates a "stationary" change type instead of + // "cancelled" change type. + // Flutter needs all the cancelled touches to be "cancelled" change types in order to correctly + // handle gesture sequence. + // We always override the change type to "cancelled". + [_flutterViewController.get() forceTouchesCancelled:touches]; + _currentTouchPointersCount -= touches.count; + if (_currentTouchPointersCount == 0) { + self.state = UIGestureRecognizerStateFailed; + _flutterViewController.reset(nil); + } +} + +- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer + shouldRecognizeSimultaneouslyWithGestureRecognizer: + (UIGestureRecognizer*)otherGestureRecognizer { + return YES; +} +@end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterView.h b/shell/platform/darwin/ios/framework/Source/FlutterView.h index 53e3bf330be6d..53dd50e45d6a8 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterView.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterView.h @@ -18,7 +18,7 @@ - (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type asBase64Encoded:(BOOL)base64Encode; -- (std::shared_ptr&)platformViewsController; +- (std::shared_ptr&)platformViewsController; /** * A callback that is called when iOS queries accessibility information of the Flutter view. diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 60bff2c482a8c..ba42b71c3e61b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -2305,7 +2305,7 @@ - (BOOL)prefersStatusBarHidden { #pragma mark - Platform views -- (std::shared_ptr&)platformViewsController { +- (std::shared_ptr&)platformViewsController { return [_engine.get() platformViewsController]; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h index f86cb98bd7578..c41533ff9c041 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h @@ -16,7 +16,7 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h" namespace flutter { -class FlutterPlatformViewsController; +class PlatformViewsController; } FLUTTER_DARWIN_EXPORT @@ -58,7 +58,7 @@ typedef void (^FlutterKeyboardAnimationCallback)(fml::TimePoint); @property(nonatomic, assign, readwrite) BOOL prefersStatusBarHidden; - (fml::WeakNSObject)getWeakNSObject; -- (std::shared_ptr&)platformViewsController; +- (std::shared_ptr&)platformViewsController; - (FlutterRestorationPlugin*)restorationPlugin; // Accepts keypress events, and then calls |nextAction| if the event was not diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewTest.mm index 4a514f8e20fdd..098c0f3eb9ead 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewTest.mm @@ -14,12 +14,12 @@ @interface FakeDelegate : NSObject @end @implementation FakeDelegate { - std::shared_ptr _platformViewsController; + std::shared_ptr _platformViewsController; } - (instancetype)init { _callbackCalled = NO; - _platformViewsController = std::shared_ptr(nullptr); + _platformViewsController = std::shared_ptr(nullptr); return self; } @@ -28,7 +28,7 @@ - (instancetype)init { return {}; } -- (std::shared_ptr&)platformViewsController { +- (std::shared_ptr&)platformViewsController { return _platformViewsController; } diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h index 9813d435a1f83..4ccd57b1bbf6b 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h @@ -43,7 +43,7 @@ class MockAccessibilityBridge : public AccessibilityBridgeIos { } void AccessibilityObjectDidBecomeFocused(int32_t id) override {} void AccessibilityObjectDidLoseFocus(int32_t id) override {} - std::shared_ptr GetPlatformViewsController() const override { + std::shared_ptr GetPlatformViewsController() const override { return nil; } std::vector observations; @@ -74,7 +74,7 @@ class MockAccessibilityBridgeNoWindow : public AccessibilityBridgeIos { } void AccessibilityObjectDidBecomeFocused(int32_t id) override {} void AccessibilityObjectDidLoseFocus(int32_t id) override {} - std::shared_ptr GetPlatformViewsController() const override { + std::shared_ptr GetPlatformViewsController() const override { return nil; } std::vector observations; diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h index c117b83593f10..4086120000072 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h @@ -51,7 +51,7 @@ class AccessibilityBridge final : public AccessibilityBridgeIos { AccessibilityBridge(FlutterViewController* view_controller, PlatformViewIOS* platform_view, - std::shared_ptr platform_views_controller, + std::shared_ptr platform_views_controller, std::unique_ptr ios_delegate = nullptr); ~AccessibilityBridge(); @@ -73,7 +73,7 @@ class AccessibilityBridge final : public AccessibilityBridgeIos { fml::WeakPtr GetWeakPtr(); - std::shared_ptr GetPlatformViewsController() const override { + std::shared_ptr GetPlatformViewsController() const override { return platform_views_controller_; }; @@ -92,7 +92,7 @@ class AccessibilityBridge final : public AccessibilityBridgeIos { FlutterViewController* view_controller_; PlatformViewIOS* platform_view_; - const std::shared_ptr platform_views_controller_; + const std::shared_ptr platform_views_controller_; // If the this id is kSemanticObjectIdInvalid, it means either nothing has // been focused or the focus is currently outside of the flutter application // (i.e. the status bar or keyboard) diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm index 7868c593f0732..7162b5c28c760 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm @@ -42,7 +42,7 @@ void PostAccessibilityNotification(UIAccessibilityNotifications notification, AccessibilityBridge::AccessibilityBridge( FlutterViewController* view_controller, PlatformViewIOS* platform_view, - std::shared_ptr platform_views_controller, + std::shared_ptr platform_views_controller, std::unique_ptr ios_delegate) : view_controller_(view_controller), platform_view_(platform_view), diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h index 9b6d5ef0a1922..0da3646a2e802 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h @@ -14,7 +14,7 @@ @class UIView; namespace flutter { -class FlutterPlatformViewsController; +class PlatformViewsController; /// Interface that represents an accessibility bridge for iOS. class AccessibilityBridgeIos { @@ -39,7 +39,7 @@ class AccessibilityBridgeIos { * The input id is the uid of the newly focused SemanticObject. */ virtual void AccessibilityObjectDidLoseFocus(int32_t id) = 0; - virtual std::shared_ptr GetPlatformViewsController() const = 0; + virtual std::shared_ptr GetPlatformViewsController() const = 0; }; } // namespace flutter diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm index 81b79a386ea70..f89c02febf616 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm @@ -279,8 +279,7 @@ - (void)testSemanticsDeallocated { /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - auto flutterPlatformViewsController = - std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(thread_task_runner); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -341,8 +340,7 @@ - (void)testSemanticsDeallocatedWithoutLoadingView { /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - auto flutterPlatformViewsController = - std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(thread_task_runner); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -389,7 +387,7 @@ - (void)testReplacedSemanticsDoesNotCleanupChildren { /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(thread_task_runner); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -486,7 +484,7 @@ - (void)testScrollableSemanticsDeallocated { /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(thread_task_runner); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -561,7 +559,7 @@ - (void)testBridgeReplacesSemanticsNode { /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - auto flutterPlatformViewsController = std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(thread_task_runner); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, @@ -2181,8 +2179,7 @@ - (void)testPlatformViewDestructorDoesNotCallSemanticsAPIs { /*is_gpu_disabled_sync_switch=*/std::make_shared()); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); - auto flutterPlatformViewsController = - std::make_shared(); + auto flutterPlatformViewsController = std::make_shared(); flutterPlatformViewsController->SetTaskRunner(thread_task_runner); OCMStub([mockFlutterViewController platformViewsController]) diff --git a/shell/platform/darwin/ios/framework/Source/platform_views_controller.h b/shell/platform/darwin/ios/framework/Source/platform_views_controller.h new file mode 100644 index 0000000000000..b98e4d8345660 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/platform_views_controller.h @@ -0,0 +1,402 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_PLATFORM_VIEWS_CONTROLLER_H_ +#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_PLATFORM_VIEWS_CONTROLLER_H_ + +#include + +#include "flutter/flow/surface.h" +#include "flutter/fml/memory/weak_ptr.h" +#include "flutter/fml/platform/darwin/scoped_nsobject.h" +#include "flutter/fml/trace_event.h" +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" +#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h" +#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h" +#import "flutter/shell/platform/darwin/ios/ios_context.h" +#include "fml/task_runner.h" +#include "impeller/base/thread_safety.h" +#include "third_party/skia/include/core/SkRect.h" + +@class FlutterTouchInterceptingView; +@class FlutterClippingMaskViewPool; + +namespace flutter { + +class IOSSurface; + +/// @brief State holder for a Flutter overlay layer. +struct OverlayLayer { + OverlayLayer(const fml::scoped_nsobject& overlay_view, + const fml::scoped_nsobject& overlay_view_wrapper, + std::unique_ptr ios_surface, + std::unique_ptr surface); + + ~OverlayLayer() = default; + + fml::scoped_nsobject overlay_view; + fml::scoped_nsobject overlay_view_wrapper; + std::unique_ptr ios_surface; + std::unique_ptr surface; + + // Whether a frame for this layer was submitted. + bool did_submit_last_frame; + + // The GrContext that is currently used by the overlay surfaces. + // We track this to know when the GrContext for the Flutter app has changed + // so we can update the overlay with the new context. + GrDirectContext* gr_context; + + void UpdateViewState(UIView* flutter_view, SkRect rect, int64_t view_id, int64_t overlay_id); +}; + +/// @brief Storage for Overlay layers across frames. +/// +/// Note: this class does not synchronize access to its layers or any layer removal. As it +/// is currently used, layers must be created on the platform thread but other methods of +/// it are called on the raster thread. This is safe as overlay layers are only ever added +/// while the raster thread is latched. +class OverlayLayerPool { + public: + OverlayLayerPool() = default; + + ~OverlayLayerPool() = default; + + /// @brief Gets a layer from the pool if available. + /// + /// The layer is marked as used until [RecycleLayers] is called. + std::shared_ptr GetNextLayer(); + + /// @brief Create a new overlay layer. + /// + /// This method can only be called on the Platform thread. + void CreateLayer(GrDirectContext* gr_context, + const std::shared_ptr& ios_context, + MTLPixelFormat pixel_format); + + /// @brief Removes unused layers from the pool. Returns the unused layers. + std::vector> RemoveUnusedLayers(); + + /// @brief Marks the layers in the pool as available for reuse. + void RecycleLayers(); + + /// @brief The count of layers currently in the pool. + size_t size() const; + + private: + OverlayLayerPool(const OverlayLayerPool&) = delete; + OverlayLayerPool& operator=(const OverlayLayerPool&) = delete; + + // The index of the entry in the layers_ vector that determines the beginning of the unused + // layers. For example, consider the following vector: + // _____ + // | 0 | + /// |---| + /// | 1 | <-- available_layer_index_ + /// |---| + /// | 2 | + /// |---| + /// + /// This indicates that entries starting from 1 can be reused meanwhile the entry at position 0 + /// cannot be reused. + size_t available_layer_index_ = 0; + std::vector> layers_; +}; + +/// @brief Composites Flutter UI and overlay layers alongside embedded UIViews. +class PlatformViewsController { + public: + PlatformViewsController(); + + ~PlatformViewsController() = default; + + /// @brief Retrieve a weak pointer to this controller. + fml::WeakPtr GetWeakPtr(); + + /// @brief Set the platform task runner used to post rendering tasks. + void SetTaskRunner(const fml::RefPtr& platform_task_runner); + + /// @brief Set the flutter view. + void SetFlutterView(UIView* flutter_view) __attribute__((cf_audited_transfer)); + + /// @brief Set the flutter view controller. + void SetFlutterViewController(UIViewController* flutter_view_controller) + __attribute__((cf_audited_transfer)); + + /// @brief Retrieve the view controller. + UIViewController* GetFlutterViewController() + __attribute__((cf_audited_transfer)); + + /// @brief set the factory used to construct embedded UI Views. + void RegisterViewFactory( + NSObject* factory, + NSString* factoryId, + FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy) + __attribute__((cf_audited_transfer)); + + /// @brief Mark the beginning of a frame and record the size of the onscreen. + void BeginFrame(SkISize frame_size); + + /// @brief Cancel the current frame, indicating that no platform views are composited. + /// + /// Additionally, reverts the composition order to its original state at the beginning of the + /// frame. + void CancelFrame(); + + /// @brief Record a platform view in the layer tree to be rendered, along with the positioning and + /// mutator parameters. + /// + /// Called from the raster thread. + void PrerollCompositeEmbeddedView(int64_t view_id, + std::unique_ptr params); + + /// @brief Returns the`FlutterTouchInterceptingView` with the provided view_id. + /// + /// Returns nil if there is no platform view with the provided id. Called + /// from the platform thread. + FlutterTouchInterceptingView* GetFlutterTouchInterceptingViewByID(int64_t view_id); + + /// @brief Determine if thread merging is required after prerolling platform views. + /// + /// Called from the raster thread. + PostPrerollResult PostPrerollAction( + const fml::RefPtr& raster_thread_merger); + + /// @brief Mark the end of a compositor frame. + /// + /// May determine changes are required to the thread merging state. + /// Called from the raster thread. + void EndFrame(bool should_resubmit_frame, + const fml::RefPtr& raster_thread_merger); + + /// @brief Returns the Canvas for the overlay slice for the given platform view. + /// + /// Called from the raster thread. + DlCanvas* CompositeEmbeddedView(int64_t view_id); + + /// @brief Discards all platform views instances and auxiliary resources. + /// + /// Called from the raster thread. + void Reset(); + + /// @brief Encode rendering for the Flutter overlay views and queue up perform platform view + /// mutations. + /// + /// Called from the raster thread. + bool SubmitFrame(GrDirectContext* gr_context, + const std::shared_ptr& ios_context, + std::unique_ptr frame); + + /// @brief Handler for platform view message channels. + void OnMethodCall(FlutterMethodCall* call, FlutterResult result) + __attribute__((cf_audited_transfer)); + + /// @brief Returns the platform view id if the platform view (or any of its descendant view) is + /// the first responder. + /// + /// Returns -1 if no such platform view is found. + long FindFirstResponderPlatformViewId(); + + /// @brief Pushes backdrop filter mutation to the mutator stack of each visited platform view. + void PushFilterToVisitedPlatformViews(const std::shared_ptr& filter, + const SkRect& filter_rect); + + /// @brief Pushes the view id of a visted platform view to the list of visied platform views. + void PushVisitedPlatformView(int64_t view_id) { visited_platform_views_.push_back(view_id); } + + // visible for testing. + size_t EmbeddedViewCount() const; + + // visible for testing. + size_t LayerPoolSize() const; + + // visible for testing. + // Returns the `FlutterPlatformView`'s `view` object associated with the view_id. + // + // If the `PlatformViewsController` does not contain any `FlutterPlatformView` object or + // a `FlutterPlatformView` object associated with the view_id cannot be found, the method + // returns nil. + UIView* GetPlatformViewByID(int64_t view_id); + + // Visible for testing. + void CompositeWithParams(int64_t view_id, const EmbeddedViewParams& params); + + // Visible for testing. + const EmbeddedViewParams& GetCompositionParams(int64_t view_id) const { + return current_composition_params_.find(view_id)->second; + } + + private: + PlatformViewsController(const PlatformViewsController&) = delete; + PlatformViewsController& operator=(const PlatformViewsController&) = delete; + + struct LayerData { + SkRect rect; + int64_t view_id; + int64_t overlay_id; + std::shared_ptr layer; + }; + + using LayersMap = std::unordered_map; + + // Update the buffers and mutate the platform views in CATransaction. + // + // Runs on the platform thread. + void PerformSubmit(const LayersMap& platform_view_layers, + std::unordered_map& current_composition_params, + const std::unordered_set& views_to_recomposite, + const std::vector& composition_order, + const std::vector>& unused_layers, + const std::vector>& surface_frames); + + /// @brief Populate any missing overlay layers. + /// + /// This requires posting a task to the platform thread and blocking on its completion. + void CreateMissingOverlays(GrDirectContext* gr_context, + const std::shared_ptr& ios_context, + size_t required_overlay_layers); + + void OnCreate(FlutterMethodCall* call, FlutterResult result) __attribute__((cf_audited_transfer)); + + void OnDispose(FlutterMethodCall* call, FlutterResult result) + __attribute__((cf_audited_transfer)); + + void OnAcceptGesture(FlutterMethodCall* call, FlutterResult result) + __attribute__((cf_audited_transfer)); + + void OnRejectGesture(FlutterMethodCall* call, FlutterResult result) + __attribute__((cf_audited_transfer)); + + /// @brief Return all views to be disposed on the platform thread. + std::vector GetViewsToDispose(); + + void ClipViewSetMaskView(UIView* clipView) __attribute__((cf_audited_transfer)); + + // Applies the mutators in the mutators_stack to the UIView chain that was constructed by + // `ReconstructClipViewsChain` + // + // 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 + // rect of the PlatformView, the clip mutator is not applied for performance optimization. + void ApplyMutators(const MutatorsStack& mutators_stack, + UIView* embedded_view, + const SkRect& bounding_rect) __attribute__((cf_audited_transfer)); + + std::shared_ptr GetExistingLayer(); + + // Runs on the platform thread. + void CreateLayer(GrDirectContext* gr_context, + const std::shared_ptr& ios_context, + MTLPixelFormat pixel_format); + + // Removes overlay views and platform views that aren't needed in the current frame. + // Must run on the platform thread. + void RemoveUnusedLayers(const std::vector>& unused_layers, + const std::vector& composition_order); + + // Appends the overlay views and platform view and sets their z index based on the composition + // order. + void BringLayersIntoView(const LayersMap& layer_map, + const std::vector& composition_order); + + // Resets the state of the frame. + void ResetFrameState(); + + // The pool of reusable view layers. The pool allows to recycle layer in each frame. + std::unique_ptr layer_pool_; + + // The platform view's |EmbedderViewSlice| keyed off the view id, which contains any subsequent + // operation until the next platform view or the end of the last leaf node in the layer tree. + // + // The Slices are deleted by the PlatformViewsController.reset(). + std::unordered_map> slices_; + + fml::scoped_nsobject channel_; + fml::scoped_nsobject flutter_view_; + fml::scoped_nsobject> flutter_view_controller_; + fml::scoped_nsobject mask_view_pool_; + std::unordered_map>> + factories_; + + // The FlutterPlatformViewGestureRecognizersBlockingPolicy for each type of platform view. + std::unordered_map + gesture_recognizers_blocking_policies_; + + /// The size of the current onscreen surface in physical pixels. + SkISize frame_size_; + + /// The task runner for posting tasks to the platform thread. + fml::RefPtr platform_task_runner_; + + /// Each of the following structs stores part of the platform view hierarchy according to its + /// ID. + /// + /// This data must only be accessed on the platform thread. + struct PlatformViewData { + fml::scoped_nsobject> view; + fml::scoped_nsobject touch_interceptor; + fml::scoped_nsobject root_view; + }; + + /// This data must only be accessed on the platform thread. + std::unordered_map platform_views_; + + /// The composition parameters for each platform view. + /// + /// This state is only modified on the raster thread. + std::unordered_map current_composition_params_; + + /// Method channel `OnDispose` calls adds the views to be disposed to this set to be disposed on + /// the next frame. + /// + /// This state is modified on both the platform and raster thread. + std::unordered_set views_to_dispose_; + + /// view IDs in composition order. + /// + /// This state is only modified on the raster thread. + std::vector composition_order_; + + /// platform view IDs visited during layer tree composition. + /// + /// This state is only modified on the raster thread. + std::vector visited_platform_views_; + + /// Only composite platform views in this set. + /// + /// This state is only modified on the raster thread. + std::unordered_set views_to_recomposite_; + +#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG + /// A set to keep track of embedded views that do not have (0, 0) origin. + /// An insertion triggers a warning message about non-zero origin logged on the debug console. + /// See https://github.com/flutter/flutter/issues/109700 for details. + std::unordered_set non_zero_origin_views_; +#endif + + /// @brief The composition order from the previous thread. + /// + /// Only accessed from the platform thread. + std::vector previous_composition_order_; + + /// Whether the previous frame had any platform views in active composition order. + /// + /// This state is tracked so that the first frame after removing the last platform view + /// runs through the platform view rendering code path, giving us a chance to remove the + /// platform view from the UIView hierarchy. + /// + /// Only accessed from the raster thread. + bool had_platform_views_ = false; + + // WeakPtrFactory must be the last member. + std::unique_ptr> weak_factory_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_PLATFORM_VIEWS_CONTROLLER_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/platform_views_controller.mm similarity index 66% rename from shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm rename to shell/platform/darwin/ios/framework/Source/platform_views_controller.mm index 5509c2245fdf6..e840ba4e0c0e3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/platform_views_controller.mm @@ -2,22 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h" - -#include -#include +#import "shell/platform/darwin/ios/framework/Source/platform_views_controller.h" #include "flow/surface_frame.h" #include "flutter/flow/view_slicer.h" #include "flutter/fml/make_copyable.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" -#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h" #import "flutter/shell/platform/darwin/ios/ios_surface.h" -#include "fml/logging.h" #include "fml/synchronization/count_down_latch.h" +#include "shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h" -FLUTTER_ASSERT_ARC +namespace { #ifdef FML_OS_IOS_SIMULATOR // The number of frames the rasterizer task runner will continue @@ -27,19 +22,39 @@ static const int kDefaultMergedLeaseDuration = 10; #endif // FML_OS_IOS_SIMULATOR -@implementation UIView (FirstResponder) -- (BOOL)flt_hasFirstResponderInViewHierarchySubtree { - if (self.isFirstResponder) { - return YES; - } - for (UIView* subview in self.subviews) { - if (subview.flt_hasFirstResponderInViewHierarchySubtree) { - return YES; - } - } - return NO; +static constexpr NSUInteger kFlutterClippingMaskViewPoolCapacity = 5; + +// Converts a SkMatrix to CATransform3D. +// +// Certain fields are ignored in CATransform3D since SkMatrix is 3x3 and CATransform3D is 4x4. +CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix& matrix) { + // Skia only supports 2D transform so we don't map z. + CATransform3D transform = CATransform3DIdentity; + transform.m11 = matrix.getScaleX(); + transform.m21 = matrix.getSkewX(); + transform.m41 = matrix.getTranslateX(); + transform.m14 = matrix.getPerspX(); + + transform.m12 = matrix.getSkewY(); + transform.m22 = matrix.getScaleY(); + transform.m42 = matrix.getTranslateY(); + transform.m24 = matrix.getPerspY(); + return transform; +} + +// Reset the anchor of `layer` to match the transform operation from flow. +// +// The position of the `layer` should be unchanged after resetting the anchor. +void ResetAnchor(CALayer* layer) { + // Flow uses (0, 0) to apply transform matrix so we need to match that in Quartz. + layer.anchorPoint = CGPointZero; + layer.position = CGPointZero; +} + +CGRect GetCGRectFromSkRect(const SkRect& clipSkRect) { + return CGRectMake(clipSkRect.fLeft, clipSkRect.fTop, clipSkRect.fRight - clipSkRect.fLeft, + clipSkRect.fBottom - clipSkRect.fTop); } -@end // Determines if the `clip_rect` from a clipRect mutator contains the // `platformview_boundingrect`. @@ -49,9 +64,9 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { // // `platformview_boundingrect` is the final bounding rect of the PlatformView in the coordinate // space where the PlatformView is displayed. -static bool ClipRectContainsPlatformViewBoundingRect(const SkRect& clip_rect, - const SkRect& platformview_boundingrect, - const SkMatrix& transform_matrix) { +bool ClipRectContainsPlatformViewBoundingRect(const SkRect& clip_rect, + const SkRect& platformview_boundingrect, + const SkMatrix& transform_matrix) { SkRect transformed_rect = transform_matrix.mapRect(clip_rect); return transformed_rect.contains(platformview_boundingrect); } @@ -64,9 +79,9 @@ static bool ClipRectContainsPlatformViewBoundingRect(const SkRect& clip_rect, // // `platformview_boundingrect` is the final bounding rect of the PlatformView in the coordinate // space where the PlatformView is displayed. -static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, - const SkRect& platformview_boundingrect, - const SkMatrix& transform_matrix) { +bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, + const SkRect& platformview_boundingrect, + const SkMatrix& transform_matrix) { SkVector upper_left = clip_rrect.radii(SkRRect::Corner::kUpperLeft_Corner); SkVector upper_right = clip_rrect.radii(SkRRect::Corner::kUpperRight_Corner); SkVector lower_right = clip_rrect.radii(SkRRect::Corner::kLowerRight_Corner); @@ -89,12 +104,27 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, return transformed_rrect.contains(platformview_boundingrect); } +} // namespace + namespace flutter { + // Becomes NO if Apple's API changes and blurred backdrop filters cannot be applied. BOOL canApplyBlurBackdrop = YES; -std::shared_ptr FlutterPlatformViewLayerPool::GetNextLayer() { - std::shared_ptr result; +OverlayLayer::OverlayLayer(const fml::scoped_nsobject& overlay_view, + const fml::scoped_nsobject& overlay_view_wrapper, + std::unique_ptr ios_surface, + std::unique_ptr surface) + : overlay_view(overlay_view), + overlay_view_wrapper(overlay_view_wrapper), + ios_surface(std::move(ios_surface)), + surface(std::move(surface)){}; + +// OverlayLayerPool +//////////////////////////////////////////////////////// + +std::shared_ptr OverlayLayerPool::GetNextLayer() { + std::shared_ptr result; if (available_layer_index_ < layers_.size()) { result = layers_[available_layer_index_]; available_layer_index_++; @@ -103,11 +133,11 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, return result; } -void FlutterPlatformViewLayerPool::CreateLayer(GrDirectContext* gr_context, - const std::shared_ptr& ios_context, - MTLPixelFormat pixel_format) { +void OverlayLayerPool::CreateLayer(GrDirectContext* gr_context, + const std::shared_ptr& ios_context, + MTLPixelFormat pixel_format) { FML_DCHECK([[NSThread currentThread] isMainThread]); - std::shared_ptr layer; + std::shared_ptr layer; fml::scoped_nsobject overlay_view; fml::scoped_nsobject overlay_view_wrapper; @@ -120,9 +150,8 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, std::unique_ptr ios_surface = IOSSurface::Create(ios_context, ca_layer); std::unique_ptr surface = ios_surface->CreateGPUSurface(); - layer = std::make_shared(std::move(overlay_view), - std::move(overlay_view_wrapper), - std::move(ios_surface), std::move(surface)); + layer = std::make_shared(std::move(overlay_view), std::move(overlay_view_wrapper), + std::move(ios_surface), std::move(surface)); } else { CGFloat screenScale = [UIScreen mainScreen].scale; overlay_view.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale @@ -134,9 +163,8 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, std::unique_ptr ios_surface = IOSSurface::Create(ios_context, ca_layer); std::unique_ptr surface = ios_surface->CreateGPUSurface(gr_context); - layer = std::make_shared(std::move(overlay_view), - std::move(overlay_view_wrapper), - std::move(ios_surface), std::move(surface)); + layer = std::make_shared(std::move(overlay_view), std::move(overlay_view_wrapper), + std::move(ios_surface), std::move(surface)); layer->gr_context = gr_context; } // The overlay view wrapper masks the overlay view. @@ -159,13 +187,12 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, layers_.push_back(layer); } -void FlutterPlatformViewLayerPool::RecycleLayers() { +void OverlayLayerPool::RecycleLayers() { available_layer_index_ = 0; } -std::vector> -FlutterPlatformViewLayerPool::RemoveUnusedLayers() { - std::vector> results; +std::vector> OverlayLayerPool::RemoveUnusedLayers() { + std::vector> results; for (size_t i = available_layer_index_; i < layers_.size(); i++) { results.push_back(layers_[i]); } @@ -181,24 +208,43 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, return results; } -size_t FlutterPlatformViewLayerPool::size() const { +size_t OverlayLayerPool::size() const { return layers_.size(); } -void FlutterPlatformViewsController::SetFlutterView(UIView* flutter_view) { +// PlatformViewsController +/////////////////////////////////////////////////// + +PlatformViewsController::PlatformViewsController() + : layer_pool_(std::make_unique()), + weak_factory_(std::make_unique>(this)) { + mask_view_pool_.reset( + [[FlutterClippingMaskViewPool alloc] initWithCapacity:kFlutterClippingMaskViewPoolCapacity]); +}; + +void PlatformViewsController::SetTaskRunner( + const fml::RefPtr& platform_task_runner) { + platform_task_runner_ = platform_task_runner; +} + +fml::WeakPtr PlatformViewsController::GetWeakPtr() { + return weak_factory_->GetWeakPtr(); +} + +void PlatformViewsController::SetFlutterView(UIView* flutter_view) { flutter_view_.reset(flutter_view); } -void FlutterPlatformViewsController::SetFlutterViewController( +void PlatformViewsController::SetFlutterViewController( UIViewController* flutter_view_controller) { flutter_view_controller_.reset(flutter_view_controller); } -UIViewController* FlutterPlatformViewsController::getFlutterViewController() { +UIViewController* PlatformViewsController::GetFlutterViewController() { return flutter_view_controller_.get(); } -void FlutterPlatformViewsController::OnMethodCall(FlutterMethodCall* call, FlutterResult result) { +void PlatformViewsController::OnMethodCall(FlutterMethodCall* call, FlutterResult result) { if ([[call method] isEqualToString:@"create"]) { OnCreate(call, result); } else if ([[call method] isEqualToString:@"dispose"]) { @@ -212,7 +258,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, } } -void FlutterPlatformViewsController::OnCreate(FlutterMethodCall* call, FlutterResult result) { +void PlatformViewsController::OnCreate(FlutterMethodCall* call, FlutterResult result) { NSDictionary* args = [call arguments]; int64_t viewId = [args[@"id"] longLongValue]; @@ -279,7 +325,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, result(nil); } -void FlutterPlatformViewsController::OnDispose(FlutterMethodCall* call, FlutterResult result) { +void PlatformViewsController::OnDispose(FlutterMethodCall* call, FlutterResult result) { NSNumber* arg = [call arguments]; int64_t viewId = [arg longLongValue]; @@ -294,8 +340,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, result(nil); } -void FlutterPlatformViewsController::OnAcceptGesture(FlutterMethodCall* call, - FlutterResult result) { +void PlatformViewsController::OnAcceptGesture(FlutterMethodCall* call, FlutterResult result) { NSDictionary* args = [call arguments]; int64_t viewId = [args[@"id"] longLongValue]; @@ -312,8 +357,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, result(nil); } -void FlutterPlatformViewsController::OnRejectGesture(FlutterMethodCall* call, - FlutterResult result) { +void PlatformViewsController::OnRejectGesture(FlutterMethodCall* call, FlutterResult result) { NSDictionary* args = [call arguments]; int64_t viewId = [args[@"id"] longLongValue]; @@ -330,7 +374,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, result(nil); } -void FlutterPlatformViewsController::RegisterViewFactory( +void PlatformViewsController::RegisterViewFactory( NSObject* factory, NSString* factoryId, FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy) { @@ -340,16 +384,16 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, gesture_recognizers_blocking_policies_[idString] = gestureRecognizerBlockingPolicy; } -void FlutterPlatformViewsController::BeginFrame(SkISize frame_size) { +void PlatformViewsController::BeginFrame(SkISize frame_size) { ResetFrameState(); frame_size_ = frame_size; } -void FlutterPlatformViewsController::CancelFrame() { +void PlatformViewsController::CancelFrame() { ResetFrameState(); } -PostPrerollResult FlutterPlatformViewsController::PostPrerollAction( +PostPrerollResult PlatformViewsController::PostPrerollAction( const fml::RefPtr& raster_thread_merger) { // TODO(jonahwilliams): remove this once Software backend is removed for iOS Sim. #ifdef FML_OS_IOS_SIMULATOR @@ -379,7 +423,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, #endif // FML_OS_IOS_SIMULATOR } -void FlutterPlatformViewsController::EndFrame( +void PlatformViewsController::EndFrame( bool should_resubmit_frame, const fml::RefPtr& raster_thread_merger) { #if FML_OS_IOS_SIMULATOR @@ -389,7 +433,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, #endif // FML_OS_IOS_SIMULATOR } -void FlutterPlatformViewsController::PushFilterToVisitedPlatformViews( +void PlatformViewsController::PushFilterToVisitedPlatformViews( const std::shared_ptr& filter, const SkRect& filter_rect) { for (int64_t id : visited_platform_views_) { @@ -399,7 +443,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, } } -void FlutterPlatformViewsController::PrerollCompositeEmbeddedView( +void PlatformViewsController::PrerollCompositeEmbeddedView( int64_t view_id, std::unique_ptr params) { SkRect view_bounds = SkRect::Make(frame_size_); @@ -418,19 +462,19 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, views_to_recomposite_.insert(view_id); } -size_t FlutterPlatformViewsController::EmbeddedViewCount() const { +size_t PlatformViewsController::EmbeddedViewCount() const { return composition_order_.size(); } -size_t FlutterPlatformViewsController::LayerPoolSize() const { +size_t PlatformViewsController::LayerPoolSize() const { return layer_pool_->size(); } -UIView* FlutterPlatformViewsController::GetPlatformViewByID(int64_t view_id) { +UIView* PlatformViewsController::GetPlatformViewByID(int64_t view_id) { return [GetFlutterTouchInterceptingViewByID(view_id) embeddedView]; } -FlutterTouchInterceptingView* FlutterPlatformViewsController::GetFlutterTouchInterceptingViewByID( +FlutterTouchInterceptingView* PlatformViewsController::GetFlutterTouchInterceptingViewByID( int64_t view_id) { if (platform_views_.empty()) { return nil; @@ -438,7 +482,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, return platform_views_[view_id].touch_interceptor.get(); } -long FlutterPlatformViewsController::FindFirstResponderPlatformViewId() { +long PlatformViewsController::FindFirstResponderPlatformViewId() { for (auto const& [id, platform_view_data] : platform_views_) { UIView* root_view = (UIView*)platform_view_data.root_view.get(); if (root_view.flt_hasFirstResponderInViewHierarchySubtree) { @@ -448,7 +492,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, return -1; } -void FlutterPlatformViewsController::ClipViewSetMaskView(UIView* clipView) { +void PlatformViewsController::ClipViewSetMaskView(UIView* clipView) { FML_DCHECK([[NSThread currentThread] isMainThread]); if (clipView.maskView) { return; @@ -462,9 +506,9 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, // 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) { +void PlatformViewsController::ApplyMutators(const MutatorsStack& mutators_stack, + UIView* embedded_view, + const SkRect& bounding_rect) { if (flutter_view_ == nullptr) { return; } @@ -525,8 +569,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, if (!canApplyBlurBackdrop || !(*iter)->GetFilterMutation().GetFilter().asBlur()) { break; } - CGRect filterRect = - flutter::GetCGRectFromSkRect((*iter)->GetFilterMutation().GetFilterRect()); + CGRect filterRect = GetCGRectFromSkRect((*iter)->GetFilterMutation().GetFilterRect()); // `filterRect` is in global coordinates. We need to convert to local space. filterRect = CGRectApplyAffineTransform( filterRect, CGAffineTransformMakeScale(1 / screenScale, 1 / screenScale)); @@ -579,7 +622,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, // the mask view, whose origin is always (0,0) to the flutter_view. transformMatrix.postTranslate(-clipView.frame.origin.x, -clipView.frame.origin.y); - embedded_view.layer.transform = flutter::GetCATransform3DFromSkMatrix(transformMatrix); + embedded_view.layer.transform = GetCATransform3DFromSkMatrix(transformMatrix); } // Composite the PlatformView with `view_id`. @@ -590,8 +633,8 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, // 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) { +void PlatformViewsController::CompositeWithParams(int64_t view_id, + const EmbeddedViewParams& params) { CGRect frame = CGRectMake(0, 0, params.sizePoints().width(), params.sizePoints().height()); FlutterTouchInterceptingView* touchInterceptor = platform_views_[view_id].touch_interceptor.get(); #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG @@ -629,11 +672,11 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, ApplyMutators(mutatorStack, touchInterceptor, rect); } -DlCanvas* FlutterPlatformViewsController::CompositeEmbeddedView(int64_t view_id) { +DlCanvas* PlatformViewsController::CompositeEmbeddedView(int64_t view_id) { return slices_[view_id]->canvas(); } -void FlutterPlatformViewsController::Reset() { +void PlatformViewsController::Reset() { // Reset will only be called from the raster thread or a merged raster/platform thread. // platform_views_ must only be modified on the platform thread, and any operations that // read or modify platform views should occur there. @@ -653,17 +696,17 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, visited_platform_views_.clear(); } -bool FlutterPlatformViewsController::SubmitFrame(GrDirectContext* gr_context, - const std::shared_ptr& ios_context, - std::unique_ptr background_frame) { - TRACE_EVENT0("flutter", "FlutterPlatformViewsController::SubmitFrame"); +bool PlatformViewsController::SubmitFrame(GrDirectContext* gr_context, + const std::shared_ptr& ios_context, + std::unique_ptr background_frame) { + TRACE_EVENT0("flutter", "PlatformViewsController::SubmitFrame"); // No platform views to render; we're done. if (flutter_view_ == nullptr || (composition_order_.empty() && !had_platform_views_)) { had_platform_views_ = false; return background_frame->Submit(); } - had_platform_views_ = true; + had_platform_views_ = !composition_order_.empty(); bool did_encode = true; LayersMap platform_view_layers; @@ -698,7 +741,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, if (overlay == overlay_layers.end()) { continue; } - std::shared_ptr layer = GetExistingLayer(); + std::shared_ptr layer = GetExistingLayer(); if (!layer) { continue; } @@ -737,8 +780,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, surface_frames.push_back(std::move(background_frame)); // Mark all layers as available, so they can be used in the next frame. - std::vector> unused_layers = - layer_pool_->RemoveUnusedLayers(); + std::vector> unused_layers = layer_pool_->RemoveUnusedLayers(); layer_pool_->RecycleLayers(); auto task = [&, // @@ -763,11 +805,10 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, return did_encode; } -void FlutterPlatformViewsController::CreateMissingOverlays( - GrDirectContext* gr_context, - const std::shared_ptr& ios_context, - size_t required_overlay_layers) { - TRACE_EVENT0("flutter", "FlutterPlatformViewsController::CreateMissingLayers"); +void PlatformViewsController::CreateMissingOverlays(GrDirectContext* gr_context, + const std::shared_ptr& ios_context, + size_t required_overlay_layers) { + TRACE_EVENT0("flutter", "PlatformViewsController::CreateMissingLayers"); if (required_overlay_layers <= layer_pool_->size()) { return; @@ -792,14 +833,14 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, } /// Update the buffers and mutate the platform views in CATransaction on the platform thread. -void FlutterPlatformViewsController::PerformSubmit( +void PlatformViewsController::PerformSubmit( const LayersMap& platform_view_layers, std::unordered_map& current_composition_params, const std::unordered_set& views_to_recomposite, const std::vector& composition_order, - const std::vector>& unused_layers, + const std::vector>& unused_layers, const std::vector>& surface_frames) { - TRACE_EVENT0("flutter", "FlutterPlatformViewsController::PerformSubmit"); + TRACE_EVENT0("flutter", "PlatformViewsController::PerformSubmit"); FML_DCHECK([[NSThread currentThread] isMainThread]); [CATransaction begin]; @@ -838,9 +879,8 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, [CATransaction commit]; } -void FlutterPlatformViewsController::BringLayersIntoView( - const LayersMap& layer_map, - const std::vector& composition_order) { +void PlatformViewsController::BringLayersIntoView(const LayersMap& layer_map, + const std::vector& composition_order) { FML_DCHECK(flutter_view_); UIView* flutter_view = flutter_view_.get(); @@ -876,20 +916,20 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, } } -std::shared_ptr FlutterPlatformViewsController::GetExistingLayer() { +std::shared_ptr PlatformViewsController::GetExistingLayer() { return layer_pool_->GetNextLayer(); } -void FlutterPlatformViewsController::CreateLayer(GrDirectContext* gr_context, - const std::shared_ptr& ios_context, - MTLPixelFormat pixel_format) { +void PlatformViewsController::CreateLayer(GrDirectContext* gr_context, + const std::shared_ptr& ios_context, + MTLPixelFormat pixel_format) { layer_pool_->CreateLayer(gr_context, ios_context, pixel_format); } -void FlutterPlatformViewLayer::UpdateViewState(UIView* flutter_view, - SkRect rect, - int64_t view_id, - int64_t overlay_id) { +void OverlayLayer::UpdateViewState(UIView* flutter_view, + SkRect rect, + int64_t view_id, + int64_t overlay_id) { UIView* overlay_view_wrapper = this->overlay_view_wrapper.get(); auto screenScale = [UIScreen mainScreen].scale; // Set the size of the overlay view wrapper. @@ -909,10 +949,10 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, [NSString stringWithFormat:@"platform_view[%lld].overlay_view[%lld]", view_id, overlay_id]; } -void FlutterPlatformViewsController::RemoveUnusedLayers( - const std::vector>& unused_layers, +void PlatformViewsController::RemoveUnusedLayers( + const std::vector>& unused_layers, const std::vector& composition_order) { - for (const std::shared_ptr& layer : unused_layers) { + for (const std::shared_ptr& layer : unused_layers) { [layer->overlay_view_wrapper removeFromSuperview]; } @@ -929,7 +969,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, } } -std::vector FlutterPlatformViewsController::GetViewsToDispose() { +std::vector PlatformViewsController::GetViewsToDispose() { std::vector views; if (views_to_dispose_.empty()) { return views; @@ -953,264 +993,10 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, return views; } -void FlutterPlatformViewsController::ResetFrameState() { +void PlatformViewsController::ResetFrameState() { slices_.clear(); composition_order_.clear(); visited_platform_views_.clear(); } } // namespace flutter - -// This recognizers delays touch events from being dispatched to the responder chain until it failed -// recognizing a gesture. -// -// We only fail this recognizer when asked to do so by the Flutter framework (which does so by -// invoking an acceptGesture method on the platform_views channel). And this is how we allow the -// Flutter framework to delay or prevent the embedded view from getting a touch sequence. -@interface DelayingGestureRecognizer : UIGestureRecognizer - -// Indicates that if the `DelayingGestureRecognizer`'s state should be set to -// `UIGestureRecognizerStateEnded` during next `touchesEnded` call. -@property(nonatomic) BOOL shouldEndInNextTouchesEnded; - -// Indicates that the `DelayingGestureRecognizer`'s `touchesEnded` has been invoked without -// setting the state to `UIGestureRecognizerStateEnded`. -@property(nonatomic) BOOL touchedEndedWithoutBlocking; - -@property(nonatomic, readonly) UIGestureRecognizer* forwardingRecognizer; - -- (instancetype)initWithTarget:(id)target - action:(SEL)action - forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer; -@end - -// While the DelayingGestureRecognizer is preventing touches from hitting the responder chain -// the touch events are not arriving to the FlutterView (and thus not arriving to the Flutter -// framework). We use this gesture recognizer to dispatch the events directly to the FlutterView -// while during this phase. -// -// If the Flutter framework decides to dispatch events to the embedded view, we fail the -// DelayingGestureRecognizer which sends the events up the responder chain. But since the events -// are handled by the embedded view they are not delivered to the Flutter framework in this phase -// as well. So during this phase as well the ForwardingGestureRecognizer dispatched the events -// directly to the FlutterView. -@interface ForwardingGestureRecognizer : UIGestureRecognizer -- (instancetype)initWithTarget:(id)target - platformViewsController: - (fml::WeakPtr)platformViewsController; -@end - -@interface FlutterTouchInterceptingView () -@property(nonatomic, weak, readonly) UIView* embeddedView; -@property(nonatomic, readonly) DelayingGestureRecognizer* delayingRecognizer; -@property(nonatomic, readonly) FlutterPlatformViewGestureRecognizersBlockingPolicy blockingPolicy; -@end - -@implementation FlutterTouchInterceptingView -- (instancetype)initWithEmbeddedView:(UIView*)embeddedView - platformViewsController: - (fml::WeakPtr)platformViewsController - gestureRecognizersBlockingPolicy: - (FlutterPlatformViewGestureRecognizersBlockingPolicy)blockingPolicy { - self = [super initWithFrame:embeddedView.frame]; - if (self) { - self.multipleTouchEnabled = YES; - _embeddedView = embeddedView; - embeddedView.autoresizingMask = - (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); - - [self addSubview:embeddedView]; - - ForwardingGestureRecognizer* forwardingRecognizer = - [[ForwardingGestureRecognizer alloc] initWithTarget:self - platformViewsController:platformViewsController]; - - _delayingRecognizer = [[DelayingGestureRecognizer alloc] initWithTarget:self - action:nil - forwardingRecognizer:forwardingRecognizer]; - _blockingPolicy = blockingPolicy; - - [self addGestureRecognizer:_delayingRecognizer]; - [self addGestureRecognizer:forwardingRecognizer]; - } - return self; -} - -- (void)releaseGesture { - self.delayingRecognizer.state = UIGestureRecognizerStateFailed; -} - -- (void)blockGesture { - switch (_blockingPolicy) { - case FlutterPlatformViewGestureRecognizersBlockingPolicyEager: - // We block all other gesture recognizers immediately in this policy. - self.delayingRecognizer.state = UIGestureRecognizerStateEnded; - break; - case FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded: - if (self.delayingRecognizer.touchedEndedWithoutBlocking) { - // If touchesEnded of the `DelayingGesureRecognizer` has been already invoked, - // we want to set the state of the `DelayingGesureRecognizer` to - // `UIGestureRecognizerStateEnded` as soon as possible. - self.delayingRecognizer.state = UIGestureRecognizerStateEnded; - } else { - // If touchesEnded of the `DelayingGesureRecognizer` has not been invoked, - // We will set a flag to notify the `DelayingGesureRecognizer` to set the state to - // `UIGestureRecognizerStateEnded` when touchesEnded is called. - self.delayingRecognizer.shouldEndInNextTouchesEnded = YES; - } - break; - default: - break; - } -} - -// We want the intercepting view to consume the touches and not pass the touches up to the parent -// view. Make the touch event method not call super will not pass the touches up to the parent view. -// Hence we overide the touch event methods and do nothing. -- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { -} - -- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { -} - -- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { -} - -- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { -} - -- (id)accessibilityContainer { - return self.flutterAccessibilityContainer; -} - -@end - -@implementation DelayingGestureRecognizer - -- (instancetype)initWithTarget:(id)target - action:(SEL)action - forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer { - self = [super initWithTarget:target action:action]; - if (self) { - self.delaysTouchesBegan = YES; - self.delaysTouchesEnded = YES; - self.delegate = self; - _shouldEndInNextTouchesEnded = NO; - _touchedEndedWithoutBlocking = NO; - _forwardingRecognizer = forwardingRecognizer; - } - return self; -} - -- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer - shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer { - // The forwarding gesture recognizer should always get all touch events, so it should not be - // required to fail by any other gesture recognizer. - return otherGestureRecognizer != _forwardingRecognizer && otherGestureRecognizer != self; -} - -- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer - shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer { - return otherGestureRecognizer == self; -} - -- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { - self.touchedEndedWithoutBlocking = NO; - [super touchesBegan:touches withEvent:event]; -} - -- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { - if (self.shouldEndInNextTouchesEnded) { - self.state = UIGestureRecognizerStateEnded; - self.shouldEndInNextTouchesEnded = NO; - } else { - self.touchedEndedWithoutBlocking = YES; - } - [super touchesEnded:touches withEvent:event]; -} - -- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { - self.state = UIGestureRecognizerStateFailed; -} -@end - -@implementation ForwardingGestureRecognizer { - // Weak reference to FlutterPlatformViewsController. The FlutterPlatformViewsController has - // a reference to the FlutterViewController, where we can dispatch pointer events to. - // - // The lifecycle of FlutterPlatformViewsController is bind to FlutterEngine, which should always - // outlives the FlutterViewController. And ForwardingGestureRecognizer is owned by a subview of - // FlutterView, so the ForwardingGestureRecognizer never out lives FlutterViewController. - // Therefore, `_platformViewsController` should never be nullptr. - fml::WeakPtr _platformViewsController; - // Counting the pointers that has started in one touch sequence. - NSInteger _currentTouchPointersCount; - // We can't dispatch events to the framework without this back pointer. - // This gesture recognizer retains the `FlutterViewController` until the - // end of a gesture sequence, that is all the touches in touchesBegan are concluded - // with |touchesCancelled| or |touchesEnded|. - fml::scoped_nsobject> _flutterViewController; -} - -- (instancetype)initWithTarget:(id)target - platformViewsController: - (fml::WeakPtr)platformViewsController { - self = [super initWithTarget:target action:nil]; - if (self) { - self.delegate = self; - FML_DCHECK(platformViewsController.get() != nullptr); - _platformViewsController = std::move(platformViewsController); - _currentTouchPointersCount = 0; - } - return self; -} - -- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { - FML_DCHECK(_currentTouchPointersCount >= 0); - if (_currentTouchPointersCount == 0) { - // At the start of each gesture sequence, we reset the `_flutterViewController`, - // so that all the touch events in the same sequence are forwarded to the same - // `_flutterViewController`. - _flutterViewController.reset(_platformViewsController->getFlutterViewController()); - } - [_flutterViewController.get() touchesBegan:touches withEvent:event]; - _currentTouchPointersCount += touches.count; -} - -- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { - [_flutterViewController.get() touchesMoved:touches withEvent:event]; -} - -- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { - [_flutterViewController.get() touchesEnded:touches withEvent:event]; - _currentTouchPointersCount -= touches.count; - // Touches in one touch sequence are sent to the touchesEnded method separately if different - // fingers stop touching the screen at different time. So one touchesEnded method triggering does - // not necessarially mean the touch sequence has ended. We Only set the state to - // UIGestureRecognizerStateFailed when all the touches in the current touch sequence is ended. - if (_currentTouchPointersCount == 0) { - self.state = UIGestureRecognizerStateFailed; - _flutterViewController.reset(nil); - } -} - -- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { - // In the event of platform view is removed, iOS generates a "stationary" change type instead of - // "cancelled" change type. - // Flutter needs all the cancelled touches to be "cancelled" change types in order to correctly - // handle gesture sequence. - // We always override the change type to "cancelled". - [_flutterViewController.get() forceTouchesCancelled:touches]; - _currentTouchPointersCount -= touches.count; - if (_currentTouchPointersCount == 0) { - self.state = UIGestureRecognizerStateFailed; - _flutterViewController.reset(nil); - } -} - -- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer - shouldRecognizeSimultaneouslyWithGestureRecognizer: - (UIGestureRecognizer*)otherGestureRecognizer { - return YES; -} -@end diff --git a/shell/platform/darwin/ios/ios_external_view_embedder.h b/shell/platform/darwin/ios/ios_external_view_embedder.h index cc7ec59b34800..a1ce91b637930 100644 --- a/shell/platform/darwin/ios/ios_external_view_embedder.h +++ b/shell/platform/darwin/ios/ios_external_view_embedder.h @@ -12,16 +12,15 @@ namespace flutter { class IOSExternalViewEmbedder : public ExternalViewEmbedder { public: - IOSExternalViewEmbedder(const std::shared_ptr& - platform_views_controller, - const std::shared_ptr& context); + IOSExternalViewEmbedder( + const std::shared_ptr& platform_views_controller, + const std::shared_ptr& context); // |ExternalViewEmbedder| virtual ~IOSExternalViewEmbedder() override; private: - const std::shared_ptr& - platform_views_controller_; + const std::shared_ptr& platform_views_controller_; std::shared_ptr ios_context_; // |ExternalViewEmbedder| diff --git a/shell/platform/darwin/ios/ios_external_view_embedder.mm b/shell/platform/darwin/ios/ios_external_view_embedder.mm index 6cd0430481251..b7c1333d91bf5 100644 --- a/shell/platform/darwin/ios/ios_external_view_embedder.mm +++ b/shell/platform/darwin/ios/ios_external_view_embedder.mm @@ -12,7 +12,7 @@ namespace flutter { IOSExternalViewEmbedder::IOSExternalViewEmbedder( - const std::shared_ptr& platform_views_controller, + const std::shared_ptr& platform_views_controller, const std::shared_ptr& context) : platform_views_controller_(platform_views_controller), ios_context_(context) { FML_CHECK(ios_context_); diff --git a/shell/platform/darwin/ios/platform_view_ios.h b/shell/platform/darwin/ios/platform_view_ios.h index fed1adfba1899..299015d785074 100644 --- a/shell/platform/darwin/ios/platform_view_ios.h +++ b/shell/platform/darwin/ios/platform_view_ios.h @@ -42,13 +42,13 @@ class PlatformViewIOS final : public PlatformView { public: PlatformViewIOS(PlatformView::Delegate& delegate, const std::shared_ptr& context, - const std::shared_ptr& platform_views_controller, + const std::shared_ptr& platform_views_controller, const flutter::TaskRunners& task_runners); explicit PlatformViewIOS( PlatformView::Delegate& delegate, IOSRenderingAPI rendering_api, - const std::shared_ptr& platform_views_controller, + const std::shared_ptr& platform_views_controller, const flutter::TaskRunners& task_runners, const std::shared_ptr& worker_task_runner, const std::shared_ptr& is_gpu_disabled_sync_switch); @@ -139,7 +139,7 @@ class PlatformViewIOS final : public PlatformView { std::mutex ios_surface_mutex_; std::unique_ptr ios_surface_; std::shared_ptr ios_context_; - const std::shared_ptr& platform_views_controller_; + const std::shared_ptr& platform_views_controller_; AccessibilityBridgeManager accessibility_bridge_; fml::scoped_nsprotocol text_input_plugin_; ScopedObserver dealloc_view_controller_observer_; diff --git a/shell/platform/darwin/ios/platform_view_ios.mm b/shell/platform/darwin/ios/platform_view_ios.mm index 4cda02ca7f3e6..1e58148319e84 100644 --- a/shell/platform/darwin/ios/platform_view_ios.mm +++ b/shell/platform/darwin/ios/platform_view_ios.mm @@ -42,7 +42,7 @@ PlatformViewIOS::PlatformViewIOS( PlatformView::Delegate& delegate, const std::shared_ptr& context, - const std::shared_ptr& platform_views_controller, + const std::shared_ptr& platform_views_controller, const flutter::TaskRunners& task_runners) : PlatformView(delegate, task_runners), ios_context_(context), @@ -54,7 +54,7 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} PlatformViewIOS::PlatformViewIOS( PlatformView::Delegate& delegate, IOSRenderingAPI rendering_api, - const std::shared_ptr& platform_views_controller, + const std::shared_ptr& platform_views_controller, const flutter::TaskRunners& task_runners, const std::shared_ptr& worker_task_runner, const std::shared_ptr& is_gpu_disabled_sync_switch) From 1513f94c3426ddbd599b3dcb82c4244ca29286c2 Mon Sep 17 00:00:00 2001 From: jonahwilliams Date: Sat, 3 Aug 2024 11:47:28 -0700 Subject: [PATCH 2/6] licenses --- ci/licenses_golden/licenses_flutter | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index f73dac77ae003..f910b73639e6f 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -43655,7 +43655,6 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterOverl ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm + ../../../flutter/LICENSE @@ -43715,6 +43714,8 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_c ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection_test.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.mm + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_views_controller.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_views_controller.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h + ../../../flutter/LICENSE @@ -46559,7 +46560,6 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlay FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm -FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm @@ -46619,6 +46619,8 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_col FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection_test.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.mm +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_views_controller.h +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_views_controller.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h From 35cd6316e469ee77900b5ddb027fa8660202758a Mon Sep 17 00:00:00 2001 From: jonahwilliams Date: Tue, 6 Aug 2024 10:46:28 -0700 Subject: [PATCH 3/6] ++ --- shell/platform/darwin/ios/BUILD.gn | 2 + .../Source/FlutterPlatformViews_Internal.mm | 25 +- .../ios/framework/Source/PlatformViewsTest.mm | 3391 +++++++++++++++++ .../ios/framework/Source/overlay_layer_pool.h | 102 + .../framework/Source/overlay_layer_pool.mm | 136 + .../Source/platform_views_controller.h | 88 +- .../Source/platform_views_controller.mm | 132 +- 7 files changed, 3652 insertions(+), 224 deletions(-) create mode 100644 shell/platform/darwin/ios/framework/Source/PlatformViewsTest.mm create mode 100644 shell/platform/darwin/ios/framework/Source/overlay_layer_pool.h create mode 100644 shell/platform/darwin/ios/framework/Source/overlay_layer_pool.mm diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 85ff9cd859100..f63d7e1f7f4ef 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -109,6 +109,8 @@ source_set("flutter_framework_source_arc") { "framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.mm", "framework/Source/connection_collection.h", "framework/Source/connection_collection.mm", + "framework/Source/overlay_layer_pool.h", + "framework/Source/overlay_layer_pool.mm", "framework/Source/platform_message_response_darwin.h", "framework/Source/platform_message_response_darwin.mm", "framework/Source/platform_views_controller.h", diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm index f9872be461b28..f1fd2ee2818df 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm @@ -468,13 +468,13 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { // We only fail this recognizer when asked to do so by the Flutter framework (which does so by // invoking an acceptGesture method on the platform_views channel). And this is how we allow the // Flutter framework to delay or prevent the embedded view from getting a touch sequence. -@interface DelayingGestureRecognizer : UIGestureRecognizer +@interface FlutterDelayingGestureRecognizer : UIGestureRecognizer -// Indicates that if the `DelayingGestureRecognizer`'s state should be set to +// Indicates that if the `FlutterDelayingGestureRecognizer`'s state should be set to // `UIGestureRecognizerStateEnded` during next `touchesEnded` call. @property(nonatomic) BOOL shouldEndInNextTouchesEnded; -// Indicates that the `DelayingGestureRecognizer`'s `touchesEnded` has been invoked without +// Indicates that the `FlutterDelayingGestureRecognizer`'s `touchesEnded` has been invoked without // setting the state to `UIGestureRecognizerStateEnded`. @property(nonatomic) BOOL touchedEndedWithoutBlocking; @@ -485,15 +485,15 @@ - (instancetype)initWithTarget:(id)target forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer; @end -// While the DelayingGestureRecognizer is preventing touches from hitting the responder chain +// While the FlutterDelayingGestureRecognizer is preventing touches from hitting the responder chain // the touch events are not arriving to the FlutterView (and thus not arriving to the Flutter // framework). We use this gesture recognizer to dispatch the events directly to the FlutterView // while during this phase. // // If the Flutter framework decides to dispatch events to the embedded view, we fail the -// DelayingGestureRecognizer which sends the events up the responder chain. But since the events -// are handled by the embedded view they are not delivered to the Flutter framework in this phase -// as well. So during this phase as well the ForwardingGestureRecognizer dispatched the events +// FlutterDelayingGestureRecognizer which sends the events up the responder chain. But since the +// events are handled by the embedded view they are not delivered to the Flutter framework in this +// phase as well. So during this phase as well the ForwardingGestureRecognizer dispatched the events // directly to the FlutterView. @interface ForwardingGestureRecognizer : UIGestureRecognizer - (instancetype)initWithTarget:(id)target @@ -503,7 +503,7 @@ - (instancetype)initWithTarget:(id)target @interface FlutterTouchInterceptingView () @property(nonatomic, weak, readonly) UIView* embeddedView; -@property(nonatomic, readonly) DelayingGestureRecognizer* delayingRecognizer; +@property(nonatomic, readonly) FlutterDelayingGestureRecognizer* delayingRecognizer; @property(nonatomic, readonly) FlutterPlatformViewGestureRecognizersBlockingPolicy blockingPolicy; @end @@ -526,9 +526,10 @@ - (instancetype)initWithEmbeddedView:(UIView*)embeddedView [[ForwardingGestureRecognizer alloc] initWithTarget:self platformViewsController:platformViewsController]; - _delayingRecognizer = [[DelayingGestureRecognizer alloc] initWithTarget:self - action:nil - forwardingRecognizer:forwardingRecognizer]; + _delayingRecognizer = + [[FlutterDelayingGestureRecognizer alloc] initWithTarget:self + action:nil + forwardingRecognizer:forwardingRecognizer]; _blockingPolicy = blockingPolicy; [self addGestureRecognizer:_delayingRecognizer]; @@ -586,7 +587,7 @@ - (id)accessibilityContainer { @end -@implementation DelayingGestureRecognizer +@implementation FlutterDelayingGestureRecognizer - (instancetype)initWithTarget:(id)target action:(SEL)action diff --git a/shell/platform/darwin/ios/framework/Source/PlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/PlatformViewsTest.mm new file mode 100644 index 0000000000000..05e999777b097 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/PlatformViewsTest.mm @@ -0,0 +1,3391 @@ +// Copyright 2013 The Flutter 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 +#import +#include "fml/synchronization/count_down_latch.h" +#include "shell/platform/darwin/ios/framework/Source/platform_views_controller.h" + +#import "flutter/fml/thread.h" +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" +#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h" +#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTouchInterceptingView_Test.h" +#import "flutter/shell/platform/darwin/ios/platform_view_ios.h" + +FLUTTER_ASSERT_ARC + +@class FlutterPlatformViewsTestMockPlatformView; +__weak static FlutterPlatformViewsTestMockPlatformView* gMockPlatformView = nil; +const float kFloatCompareEpsilon = 0.001; + +@interface FlutterPlatformViewsTestMockPlatformView : UIView +@end +@implementation FlutterPlatformViewsTestMockPlatformView + +- (instancetype)init { + self = [super init]; + if (self) { + gMockPlatformView = self; + } + return self; +} + +- (void)dealloc { + gMockPlatformView = nil; +} + +@end + +@interface FlutterPlatformViewsTestMockFlutterPlatformView : NSObject +@property(nonatomic, strong) UIView* view; +@property(nonatomic, assign) BOOL viewCreated; +@end + +@implementation FlutterPlatformViewsTestMockFlutterPlatformView + +- (instancetype)init { + if (self = [super init]) { + _view = [[FlutterPlatformViewsTestMockPlatformView alloc] init]; + _viewCreated = NO; + } + return self; +} + +- (UIView*)view { + [self checkViewCreatedOnce]; + return _view; +} + +- (void)checkViewCreatedOnce { + if (self.viewCreated) { + abort(); + } + self.viewCreated = YES; +} + +@end + +@interface FlutterPlatformViewsTestMockFlutterPlatformFactory + : NSObject +@end + +@implementation FlutterPlatformViewsTestMockFlutterPlatformFactory +- (NSObject*)createWithFrame:(CGRect)frame + viewIdentifier:(int64_t)viewId + arguments:(id _Nullable)args { + return [[FlutterPlatformViewsTestMockFlutterPlatformView alloc] init]; +} + +@end + +namespace flutter { +namespace { +class FlutterPlatformViewsTestMockPlatformViewDelegate : public PlatformView::Delegate { + public: + void OnPlatformViewCreated(std::unique_ptr surface) override {} + void OnPlatformViewDestroyed() override {} + void OnPlatformViewScheduleFrame() override {} + void OnPlatformViewAddView(int64_t view_id, + const ViewportMetrics& viewport_metrics, + AddViewCallback callback) override {} + void OnPlatformViewRemoveView(int64_t view_id, RemoveViewCallback callback) override {} + void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override {} + void OnPlatformViewSetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) override {} + const flutter::Settings& OnPlatformViewGetSettings() const override { return settings_; } + void OnPlatformViewDispatchPlatformMessage(std::unique_ptr message) override {} + void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr packet) override { + } + void OnPlatformViewDispatchSemanticsAction(int32_t id, + SemanticsAction action, + fml::MallocMapping args) override {} + void OnPlatformViewSetSemanticsEnabled(bool enabled) override {} + void OnPlatformViewSetAccessibilityFeatures(int32_t flags) override {} + void OnPlatformViewRegisterTexture(std::shared_ptr texture) override {} + void OnPlatformViewUnregisterTexture(int64_t texture_id) override {} + void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {} + + void LoadDartDeferredLibrary(intptr_t loading_unit_id, + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions) override { + } + void LoadDartDeferredLibraryError(intptr_t loading_unit_id, + const std::string error_message, + bool transient) override {} + void UpdateAssetResolverByType(std::unique_ptr updated_asset_resolver, + flutter::AssetResolver::AssetResolverType type) override {} + + flutter::Settings settings_; +}; + +BOOL BlurRadiusEqualToBlurRadius(CGFloat radius1, CGFloat radius2) { + const CGFloat epsilon = 0.01; + return std::abs(radius1 - radius2) < epsilon; +} + +} // namespace +} // namespace flutter + +@interface FlutterPlatformViewsTest : XCTestCase +@end + +@implementation FlutterPlatformViewsTest + +namespace { +fml::RefPtr GetDefaultTaskRunner() { + fml::MessageLoop::EnsureInitializedForCurrentThread(); + return fml::MessageLoop::GetCurrent().GetTaskRunner(); +} +} // namespace + +- (void)testFlutterViewOnlyCreateOnceInOneFrame { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // Create embedded view params + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack + SkMatrix screenScaleMatrix = + SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); + stack.PushTransform(screenScaleMatrix); + // Push a translate matrix + SkMatrix translateMatrix = SkMatrix::Translate(100, 100); + stack.PushTransform(translateMatrix); + SkMatrix finalMatrix; + finalMatrix.setConcat(screenScaleMatrix, translateMatrix); + + auto embeddedViewParams = + std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + + XCTAssertNotNil(gMockPlatformView); + + flutterPlatformViewsController->Reset(); +} + +- (void)testCanCreatePlatformViewWithoutFlutterView { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); +} + +- (void)testChildClippingViewHitTests { + ChildClippingView* childClippingView = + [[ChildClippingView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; + UIView* childView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)]; + [childClippingView addSubview:childView]; + + XCTAssertFalse([childClippingView pointInside:CGPointMake(50, 50) withEvent:nil]); + XCTAssertFalse([childClippingView pointInside:CGPointMake(99, 100) withEvent:nil]); + XCTAssertFalse([childClippingView pointInside:CGPointMake(100, 99) withEvent:nil]); + XCTAssertFalse([childClippingView pointInside:CGPointMake(201, 200) withEvent:nil]); + XCTAssertFalse([childClippingView pointInside:CGPointMake(200, 201) withEvent:nil]); + XCTAssertFalse([childClippingView pointInside:CGPointMake(99, 200) withEvent:nil]); + XCTAssertFalse([childClippingView pointInside:CGPointMake(200, 299) withEvent:nil]); + + XCTAssertTrue([childClippingView pointInside:CGPointMake(150, 150) withEvent:nil]); + XCTAssertTrue([childClippingView pointInside:CGPointMake(100, 100) withEvent:nil]); + XCTAssertTrue([childClippingView pointInside:CGPointMake(199, 100) withEvent:nil]); + XCTAssertTrue([childClippingView pointInside:CGPointMake(100, 199) withEvent:nil]); + XCTAssertTrue([childClippingView pointInside:CGPointMake(199, 199) withEvent:nil]); +} + +- (void)testReleasesBackdropFilterSubviewsOnChildClippingViewDealloc { + __weak NSMutableArray* weakBackdropFilterSubviews = nil; + __weak UIVisualEffectView* weakVisualEffectView1 = nil; + __weak UIVisualEffectView* weakVisualEffectView2 = nil; + + @autoreleasepool { + ChildClippingView* clippingView = [[ChildClippingView alloc] initWithFrame:CGRectZero]; + UIVisualEffectView* visualEffectView1 = [[UIVisualEffectView alloc] + initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; + weakVisualEffectView1 = visualEffectView1; + PlatformViewFilter* platformViewFilter1 = + [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) + blurRadius:5 + visualEffectView:visualEffectView1]; + + [clippingView applyBlurBackdropFilters:@[ platformViewFilter1 ]]; + + // Replace the blur filter to validate the original and new UIVisualEffectView are released. + UIVisualEffectView* visualEffectView2 = [[UIVisualEffectView alloc] + initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]]; + weakVisualEffectView2 = visualEffectView2; + PlatformViewFilter* platformViewFilter2 = + [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) + blurRadius:5 + visualEffectView:visualEffectView2]; + [clippingView applyBlurBackdropFilters:@[ platformViewFilter2 ]]; + + weakBackdropFilterSubviews = clippingView.backdropFilterSubviews; + XCTAssertNotNil(weakBackdropFilterSubviews); + clippingView = nil; + } + XCTAssertNil(weakBackdropFilterSubviews); + XCTAssertNil(weakVisualEffectView1); + XCTAssertNil(weakVisualEffectView2); +} + +- (void)testApplyBackdropFilter { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // Create embedded view params + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack + CGFloat screenScale = [UIScreen mainScreen].scale; + SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); + stack.PushTransform(screenScaleMatrix); + // Push a backdrop filter + auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); + stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + + auto embeddedViewParams = + std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); + ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; + [flutterView addSubview:childClippingView]; + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + // childClippingView has visual effect view with the correct configurations. + NSUInteger numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:5]) { + numberOfExpectedVisualEffectView++; + } + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 1u); +} + +- (void)testApplyBackdropFilterWithCorrectFrame { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // Create embedded view params + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack + CGFloat screenScale = [UIScreen mainScreen].scale; + SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); + stack.PushTransform(screenScaleMatrix); + // Push a backdrop filter + auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); + stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 8, screenScale * 8)); + + auto embeddedViewParams = + std::make_unique(screenScaleMatrix, SkSize::Make(5, 10), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); + ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; + [flutterView addSubview:childClippingView]; + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + // childClippingView has visual effect view with the correct configurations. + NSUInteger numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 5, 8) + inputRadius:5]) { + numberOfExpectedVisualEffectView++; + } + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 1u); +} + +- (void)testApplyMultipleBackdropFilters { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // Create embedded view params + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack + CGFloat screenScale = [UIScreen mainScreen].scale; + SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); + stack.PushTransform(screenScaleMatrix); + // Push backdrop filters + for (int i = 0; i < 50; i++) { + auto filter = std::make_shared(i, 2, flutter::DlTileMode::kClamp); + stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + } + + auto embeddedViewParams = + std::make_unique(screenScaleMatrix, SkSize::Make(20, 20), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); + ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; + [flutterView addSubview:childClippingView]; + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + NSUInteger numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 50u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)numberOfExpectedVisualEffectView]) { + numberOfExpectedVisualEffectView++; + } + } + XCTAssertEqual(numberOfExpectedVisualEffectView, (NSUInteger)numberOfExpectedVisualEffectView); +} + +- (void)testAddBackdropFilters { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // Create embedded view params + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack + CGFloat screenScale = [UIScreen mainScreen].scale; + SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); + stack.PushTransform(screenScaleMatrix); + // Push a backdrop filter + auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); + stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + + auto embeddedViewParams = + std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); + ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; + [flutterView addSubview:childClippingView]; + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + NSMutableArray* originalVisualEffectViews = [[NSMutableArray alloc] init]; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(originalVisualEffectViews.count, 1u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)5]) { + [originalVisualEffectViews addObject:subview]; + } + } + XCTAssertEqual(originalVisualEffectViews.count, 1u); + + // + // Simulate adding 1 backdrop filter (create a new mutators stack) + // Create embedded view params + flutter::MutatorsStack stack2; + // Layer tree always pushes a screen scale factor to the stack + stack2.PushTransform(screenScaleMatrix); + // Push backdrop filters + for (int i = 0; i < 2; i++) { + stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + } + + embeddedViewParams = std::make_unique(screenScaleMatrix, + SkSize::Make(10, 10), stack2); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + NSMutableArray* newVisualEffectViews = [[NSMutableArray alloc] init]; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(newVisualEffectViews.count, 2u); + + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)5]) { + [newVisualEffectViews addObject:subview]; + } + } + XCTAssertEqual(newVisualEffectViews.count, 2u); + for (NSUInteger i = 0; i < originalVisualEffectViews.count; i++) { + UIView* originalView = originalVisualEffectViews[i]; + UIView* newView = newVisualEffectViews[i]; + // Compare reference. + XCTAssertEqual(originalView, newView); + id mockOrignalView = OCMPartialMock(originalView); + OCMReject([mockOrignalView removeFromSuperview]); + [mockOrignalView stopMocking]; + } +} + +- (void)testRemoveBackdropFilters { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // Create embedded view params + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack + CGFloat screenScale = [UIScreen mainScreen].scale; + SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); + stack.PushTransform(screenScaleMatrix); + // Push backdrop filters + auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); + for (int i = 0; i < 5; i++) { + stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + } + + auto embeddedViewParams = + std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); + ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; + [flutterView addSubview:childClippingView]; + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + NSMutableArray* originalVisualEffectViews = [[NSMutableArray alloc] init]; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(originalVisualEffectViews.count, 5u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)5]) { + [originalVisualEffectViews addObject:subview]; + } + } + + // Simulate removing 1 backdrop filter (create a new mutators stack) + // Create embedded view params + flutter::MutatorsStack stack2; + // Layer tree always pushes a screen scale factor to the stack + stack2.PushTransform(screenScaleMatrix); + // Push backdrop filters + for (int i = 0; i < 4; i++) { + stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + } + + embeddedViewParams = std::make_unique(screenScaleMatrix, + SkSize::Make(10, 10), stack2); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + NSMutableArray* newVisualEffectViews = [[NSMutableArray alloc] init]; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(newVisualEffectViews.count, 4u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)5]) { + [newVisualEffectViews addObject:subview]; + } + } + XCTAssertEqual(newVisualEffectViews.count, 4u); + + for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) { + UIView* newView = newVisualEffectViews[i]; + id mockNewView = OCMPartialMock(newView); + UIView* originalView = originalVisualEffectViews[i]; + // Compare reference. + XCTAssertEqual(originalView, newView); + OCMReject([mockNewView removeFromSuperview]); + [mockNewView stopMocking]; + } + + // Simulate removing all backdrop filters (replace the mutators stack) + // Update embedded view params, delete except screenScaleMatrix + for (int i = 0; i < 5; i++) { + stack2.Pop(); + } + // No backdrop filters in the stack, so no nothing to push + + embeddedViewParams = std::make_unique(screenScaleMatrix, + SkSize::Make(10, 10), stack2); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + NSUInteger numberOfExpectedVisualEffectView = 0u; + for (UIView* subview in childClippingView.subviews) { + if ([subview isKindOfClass:[UIVisualEffectView class]]) { + numberOfExpectedVisualEffectView++; + } + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 0u); +} + +- (void)testEditBackdropFilters { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // Create embedded view params + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack + CGFloat screenScale = [UIScreen mainScreen].scale; + SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); + stack.PushTransform(screenScaleMatrix); + // Push backdrop filters + auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); + for (int i = 0; i < 5; i++) { + stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + } + + auto embeddedViewParams = + std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); + ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; + [flutterView addSubview:childClippingView]; + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + NSMutableArray* originalVisualEffectViews = [[NSMutableArray alloc] init]; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(originalVisualEffectViews.count, 5u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)5]) { + [originalVisualEffectViews addObject:subview]; + } + } + + // Simulate editing 1 backdrop filter in the middle of the stack (create a new mutators stack) + // Create embedded view params + flutter::MutatorsStack stack2; + // Layer tree always pushes a screen scale factor to the stack + stack2.PushTransform(screenScaleMatrix); + // Push backdrop filters + for (int i = 0; i < 5; i++) { + if (i == 3) { + auto filter2 = + std::make_shared(2, 5, flutter::DlTileMode::kClamp); + + stack2.PushBackdropFilter(filter2, + SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + continue; + } + + stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + } + + embeddedViewParams = std::make_unique(screenScaleMatrix, + SkSize::Make(10, 10), stack2); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + NSMutableArray* newVisualEffectViews = [[NSMutableArray alloc] init]; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(newVisualEffectViews.count, 5u); + CGFloat expectInputRadius = 5; + if (newVisualEffectViews.count == 3) { + expectInputRadius = 2; + } + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)expectInputRadius]) { + [newVisualEffectViews addObject:subview]; + } + } + XCTAssertEqual(newVisualEffectViews.count, 5u); + for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) { + UIView* newView = newVisualEffectViews[i]; + id mockNewView = OCMPartialMock(newView); + UIView* originalView = originalVisualEffectViews[i]; + // Compare reference. + XCTAssertEqual(originalView, newView); + OCMReject([mockNewView removeFromSuperview]); + [mockNewView stopMocking]; + } + [newVisualEffectViews removeAllObjects]; + + // Simulate editing 1 backdrop filter in the beginning of the stack (replace the mutators stack) + // Update embedded view params, delete except screenScaleMatrix + for (int i = 0; i < 5; i++) { + stack2.Pop(); + } + // Push backdrop filters + for (int i = 0; i < 5; i++) { + if (i == 0) { + auto filter2 = + std::make_shared(2, 5, flutter::DlTileMode::kClamp); + stack2.PushBackdropFilter(filter2, + SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + continue; + } + + stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + } + + embeddedViewParams = std::make_unique(screenScaleMatrix, + SkSize::Make(10, 10), stack2); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(newVisualEffectViews.count, 5u); + CGFloat expectInputRadius = 5; + if (newVisualEffectViews.count == 0) { + expectInputRadius = 2; + } + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)expectInputRadius]) { + [newVisualEffectViews addObject:subview]; + } + } + for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) { + UIView* newView = newVisualEffectViews[i]; + id mockNewView = OCMPartialMock(newView); + UIView* originalView = originalVisualEffectViews[i]; + // Compare reference. + XCTAssertEqual(originalView, newView); + OCMReject([mockNewView removeFromSuperview]); + [mockNewView stopMocking]; + } + [newVisualEffectViews removeAllObjects]; + + // Simulate editing 1 backdrop filter in the end of the stack (replace the mutators stack) + // Update embedded view params, delete except screenScaleMatrix + for (int i = 0; i < 5; i++) { + stack2.Pop(); + } + // Push backdrop filters + for (int i = 0; i < 5; i++) { + if (i == 4) { + auto filter2 = + std::make_shared(2, 5, flutter::DlTileMode::kClamp); + stack2.PushBackdropFilter(filter2, + SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + continue; + } + + stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + } + + embeddedViewParams = std::make_unique(screenScaleMatrix, + SkSize::Make(10, 10), stack2); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(newVisualEffectViews.count, 5u); + CGFloat expectInputRadius = 5; + if (newVisualEffectViews.count == 4) { + expectInputRadius = 2; + } + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)expectInputRadius]) { + [newVisualEffectViews addObject:subview]; + } + } + XCTAssertEqual(newVisualEffectViews.count, 5u); + + for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) { + UIView* newView = newVisualEffectViews[i]; + id mockNewView = OCMPartialMock(newView); + UIView* originalView = originalVisualEffectViews[i]; + // Compare reference. + XCTAssertEqual(originalView, newView); + OCMReject([mockNewView removeFromSuperview]); + [mockNewView stopMocking]; + } + [newVisualEffectViews removeAllObjects]; + + // Simulate editing all backdrop filters in the stack (replace the mutators stack) + // Update embedded view params, delete except screenScaleMatrix + for (int i = 0; i < 5; i++) { + stack2.Pop(); + } + // Push backdrop filters + for (int i = 0; i < 5; i++) { + auto filter2 = std::make_shared(i, 2, flutter::DlTileMode::kClamp); + + stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + } + + embeddedViewParams = std::make_unique(screenScaleMatrix, + SkSize::Make(10, 10), stack2); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(newVisualEffectViews.count, 5u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)newVisualEffectViews.count]) { + [newVisualEffectViews addObject:subview]; + } + } + XCTAssertEqual(newVisualEffectViews.count, 5u); + + for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) { + UIView* newView = newVisualEffectViews[i]; + id mockNewView = OCMPartialMock(newView); + UIView* originalView = originalVisualEffectViews[i]; + // Compare reference. + XCTAssertEqual(originalView, newView); + OCMReject([mockNewView removeFromSuperview]); + [mockNewView stopMocking]; + } + [newVisualEffectViews removeAllObjects]; +} + +- (void)testApplyBackdropFilterNotDlBlurImageFilter { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // Create embedded view params + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack + CGFloat screenScale = [UIScreen mainScreen].scale; + SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); + stack.PushTransform(screenScaleMatrix); + // Push a dilate backdrop filter + auto dilateFilter = std::make_shared(5, 2); + stack.PushBackdropFilter(dilateFilter, SkRect::MakeEmpty()); + + auto embeddedViewParams = + std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); + ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; + + [flutterView addSubview:childClippingView]; + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + NSUInteger numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if ([subview isKindOfClass:[UIVisualEffectView class]]) { + numberOfExpectedVisualEffectView++; + } + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 0u); + + // Simulate adding a non-DlBlurImageFilter in the middle of the stack (create a new mutators + // stack) Create embedded view params + flutter::MutatorsStack stack2; + // Layer tree always pushes a screen scale factor to the stack + stack2.PushTransform(screenScaleMatrix); + // Push backdrop filters and dilate filter + auto blurFilter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); + + for (int i = 0; i < 5; i++) { + if (i == 2) { + stack2.PushBackdropFilter(dilateFilter, + SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + continue; + } + + stack2.PushBackdropFilter(blurFilter, + SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + } + + embeddedViewParams = std::make_unique(screenScaleMatrix, + SkSize::Make(10, 10), stack2); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)5]) { + numberOfExpectedVisualEffectView++; + } + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 4u); + + // Simulate adding a non-DlBlurImageFilter to the beginning of the stack (replace the mutators + // stack) Update embedded view params, delete except screenScaleMatrix + for (int i = 0; i < 5; i++) { + stack2.Pop(); + } + // Push backdrop filters and dilate filter + for (int i = 0; i < 5; i++) { + if (i == 0) { + stack2.PushBackdropFilter(dilateFilter, + SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + continue; + } + + stack2.PushBackdropFilter(blurFilter, + SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + } + + embeddedViewParams = std::make_unique(screenScaleMatrix, + SkSize::Make(10, 10), stack2); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)5]) { + numberOfExpectedVisualEffectView++; + } + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 4u); + + // Simulate adding a non-DlBlurImageFilter to the end of the stack (replace the mutators stack) + // Update embedded view params, delete except screenScaleMatrix + for (int i = 0; i < 5; i++) { + stack2.Pop(); + } + // Push backdrop filters and dilate filter + for (int i = 0; i < 5; i++) { + if (i == 4) { + stack2.PushBackdropFilter(dilateFilter, + SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + continue; + } + + stack2.PushBackdropFilter(blurFilter, + SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + } + + embeddedViewParams = std::make_unique(screenScaleMatrix, + SkSize::Make(10, 10), stack2); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)5]) { + numberOfExpectedVisualEffectView++; + } + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 4u); + + // Simulate adding only non-DlBlurImageFilter to the stack (replace the mutators stack) + // Update embedded view params, delete except screenScaleMatrix + for (int i = 0; i < 5; i++) { + stack2.Pop(); + } + // Push dilate filters + for (int i = 0; i < 5; i++) { + stack2.PushBackdropFilter(dilateFilter, + SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + } + + embeddedViewParams = std::make_unique(screenScaleMatrix, + SkSize::Make(10, 10), stack2); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if ([subview isKindOfClass:[UIVisualEffectView class]]) { + numberOfExpectedVisualEffectView++; + } + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 0u); +} + +- (void)testApplyBackdropFilterCorrectAPI { + [PlatformViewFilter resetPreparation]; + // The gaussianBlur filter is extracted from UIVisualEffectView. + // Each test requires a new PlatformViewFilter + // Valid UIVisualEffectView API + UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc] + initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; + PlatformViewFilter* platformViewFilter = + [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) + blurRadius:5 + visualEffectView:visualEffectView]; + XCTAssertNotNil(platformViewFilter); +} + +- (void)testApplyBackdropFilterAPIChangedInvalidUIVisualEffectView { + [PlatformViewFilter resetPreparation]; + UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc] init]; + PlatformViewFilter* platformViewFilter = + [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) + blurRadius:5 + visualEffectView:visualEffectView]; + XCTAssertNil(platformViewFilter); +} + +- (void)testApplyBackdropFilterAPIChangedNoGaussianBlurFilter { + [PlatformViewFilter resetPreparation]; + UIVisualEffectView* editedUIVisualEffectView = [[UIVisualEffectView alloc] + initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; + NSArray* subviews = editedUIVisualEffectView.subviews; + for (UIView* view in subviews) { + if ([NSStringFromClass([view class]) hasSuffix:@"BackdropView"]) { + for (CIFilter* filter in view.layer.filters) { + if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) { + [filter setValue:@"notGaussianBlur" forKey:@"name"]; + break; + } + } + break; + } + } + PlatformViewFilter* platformViewFilter = + [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) + blurRadius:5 + visualEffectView:editedUIVisualEffectView]; + XCTAssertNil(platformViewFilter); +} + +- (void)testApplyBackdropFilterAPIChangedInvalidInputRadius { + [PlatformViewFilter resetPreparation]; + UIVisualEffectView* editedUIVisualEffectView = [[UIVisualEffectView alloc] + initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; + NSArray* subviews = editedUIVisualEffectView.subviews; + for (UIView* view in subviews) { + if ([NSStringFromClass([view class]) hasSuffix:@"BackdropView"]) { + for (CIFilter* filter in view.layer.filters) { + if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) { + [filter setValue:@"invalidInputRadius" forKey:@"inputRadius"]; + break; + } + } + break; + } + } + + PlatformViewFilter* platformViewFilter = + [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) + blurRadius:5 + visualEffectView:editedUIVisualEffectView]; + XCTAssertNil(platformViewFilter); +} + +- (void)testBackdropFilterVisualEffectSubviewBackgroundColor { + __weak UIVisualEffectView* weakVisualEffectView; + + @autoreleasepool { + UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc] + initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; + weakVisualEffectView = visualEffectView; + PlatformViewFilter* platformViewFilter = + [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) + blurRadius:5 + visualEffectView:visualEffectView]; + CGColorRef visualEffectSubviewBackgroundColor = nil; + for (UIView* view in [platformViewFilter backdropFilterView].subviews) { + if ([NSStringFromClass([view class]) hasSuffix:@"VisualEffectSubview"]) { + visualEffectSubviewBackgroundColor = view.layer.backgroundColor; + } + } + XCTAssertTrue( + CGColorEqualToColor(visualEffectSubviewBackgroundColor, UIColor.clearColor.CGColor)); + } + XCTAssertNil(weakVisualEffectView); +} + +- (void)testCompositePlatformView { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // Create embedded view params + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack + SkMatrix screenScaleMatrix = + SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); + stack.PushTransform(screenScaleMatrix); + // Push a translate matrix + SkMatrix translateMatrix = SkMatrix::Translate(100, 100); + stack.PushTransform(translateMatrix); + SkMatrix finalMatrix; + finalMatrix.setConcat(screenScaleMatrix, translateMatrix); + + auto embeddedViewParams = + std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + CGRect platformViewRectInFlutterView = [gMockPlatformView convertRect:gMockPlatformView.bounds + toView:flutterView]; + XCTAssertTrue(CGRectEqualToRect(platformViewRectInFlutterView, CGRectMake(100, 100, 300, 300))); +} + +- (void)testBackdropFilterCorrectlyPushedAndReset { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // Create embedded view params + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack + CGFloat screenScale = [UIScreen mainScreen].scale; + SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); + stack.PushTransform(screenScaleMatrix); + + auto embeddedViewParams = + std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); + + flutterPlatformViewsController->BeginFrame(SkISize::Make(0, 0)); + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->PushVisitedPlatformView(2); + auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); + flutterPlatformViewsController->PushFilterToVisitedPlatformViews( + filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); + ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; + [flutterView addSubview:childClippingView]; + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + // childClippingView has visual effect view with the correct configurations. + NSUInteger numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:5]) { + numberOfExpectedVisualEffectView++; + } + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 1u); + + // New frame, with no filter pushed. + auto embeddedViewParams2 = + std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); + flutterPlatformViewsController->BeginFrame(SkISize::Make(0, 0)); + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams2)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + numberOfExpectedVisualEffectView++; + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 0u); +} + +- (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // Create embedded view params + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack + SkMatrix screenScaleMatrix = + SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); + stack.PushTransform(screenScaleMatrix); + // Push a rotate matrix + SkMatrix rotateMatrix; + rotateMatrix.setRotate(10); + stack.PushTransform(rotateMatrix); + SkMatrix finalMatrix; + finalMatrix.setConcat(screenScaleMatrix, rotateMatrix); + + auto embeddedViewParams = + std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + CGRect platformViewRectInFlutterView = [gMockPlatformView convertRect:gMockPlatformView.bounds + toView:flutterView]; + XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); + ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; + // The childclippingview's frame is set based on flow, but the platform view's frame is set based + // on quartz. Although they should be the same, but we should tolerate small floating point + // errors. + XCTAssertLessThan(fabs(platformViewRectInFlutterView.origin.x - childClippingView.frame.origin.x), + kFloatCompareEpsilon); + XCTAssertLessThan(fabs(platformViewRectInFlutterView.origin.y - childClippingView.frame.origin.y), + kFloatCompareEpsilon); + XCTAssertLessThan( + fabs(platformViewRectInFlutterView.size.width - childClippingView.frame.size.width), + kFloatCompareEpsilon); + XCTAssertLessThan( + fabs(platformViewRectInFlutterView.size.height - childClippingView.frame.size.height), + kFloatCompareEpsilon); +} + +- (void)testClipsDoNotInterceptWithPlatformViewShouldNotAddMaskView { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // Create embedded view params. + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack. + SkMatrix screenScaleMatrix = + SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); + stack.PushTransform(screenScaleMatrix); + SkMatrix translateMatrix = SkMatrix::Translate(5, 5); + // The platform view's rect for this test will be (5, 5, 10, 10). + stack.PushTransform(translateMatrix); + // Push a clip rect, big enough to contain the entire platform view bound. + SkRect rect = SkRect::MakeXYWH(0, 0, 25, 25); + stack.PushClipRect(rect); + // Push a clip rrect, big enough to contain the entire platform view bound without clipping it. + // Make the origin (-1, -1) so that the top left rounded corner isn't clipping the PlatformView. + SkRect rect_for_rrect = SkRect::MakeXYWH(-1, -1, 25, 25); + SkRRect rrect = SkRRect::MakeRectXY(rect_for_rrect, 1, 1); + stack.PushClipRRect(rrect); + + auto embeddedViewParams = std::make_unique( + SkMatrix::Concat(screenScaleMatrix, translateMatrix), SkSize::Make(5, 5), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + gMockPlatformView.backgroundColor = UIColor.redColor; + XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); + ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; + [flutterView addSubview:childClippingView]; + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + XCTAssertNil(childClippingView.maskView); +} + +- (void)testClipRRectOnlyHasCornersInterceptWithPlatformViewShouldAddMaskView { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // Create embedded view params + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack. + SkMatrix screenScaleMatrix = + SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); + stack.PushTransform(screenScaleMatrix); + SkMatrix translateMatrix = SkMatrix::Translate(5, 5); + // The platform view's rect for this test will be (5, 5, 10, 10). + stack.PushTransform(translateMatrix); + + // Push a clip rrect, the rect of the rrect is the same as the PlatformView of the corner should. + // clip the PlatformView. + SkRect rect_for_rrect = SkRect::MakeXYWH(0, 0, 10, 10); + SkRRect rrect = SkRRect::MakeRectXY(rect_for_rrect, 1, 1); + stack.PushClipRRect(rrect); + + auto embeddedViewParams = std::make_unique( + SkMatrix::Concat(screenScaleMatrix, translateMatrix), SkSize::Make(5, 5), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + gMockPlatformView.backgroundColor = UIColor.redColor; + XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); + ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; + [flutterView addSubview:childClippingView]; + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + XCTAssertNotNil(childClippingView.maskView); +} + +- (void)testClipRect { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // Create embedded view params + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack + SkMatrix screenScaleMatrix = + SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); + stack.PushTransform(screenScaleMatrix); + // Push a clip rect + SkRect rect = SkRect::MakeXYWH(2, 2, 3, 3); + stack.PushClipRect(rect); + + auto embeddedViewParams = + std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + gMockPlatformView.backgroundColor = UIColor.redColor; + XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); + ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; + [flutterView addSubview:childClippingView]; + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + CGRect insideClipping = CGRectMake(2, 2, 3, 3); + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + CGPoint point = CGPointMake(i, j); + int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView]; + if (CGRectContainsPoint(insideClipping, point)) { + XCTAssertEqual(alpha, 255); + } else { + XCTAssertEqual(alpha, 0); + } + } + } +} + +- (void)testClipRRect { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // Create embedded view params + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack + SkMatrix screenScaleMatrix = + SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); + stack.PushTransform(screenScaleMatrix); + // Push a clip rrect + SkRRect rrect = SkRRect::MakeRectXY(SkRect::MakeXYWH(2, 2, 6, 6), 1, 1); + stack.PushClipRRect(rrect); + + auto embeddedViewParams = + std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + gMockPlatformView.backgroundColor = UIColor.redColor; + XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); + ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; + [flutterView addSubview:childClippingView]; + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + /* + ClippingMask outterClipping + 2 3 4 5 6 7 2 3 4 5 6 7 + 2 / - - - - \ 2 + - - - - + + 3 | | 3 | | + 4 | | 4 | | + 5 | | 5 | | + 6 | | 6 | | + 7 \ - - - - / 7 + - - - - + + + innerClipping1 innerClipping2 + 2 3 4 5 6 7 2 3 4 5 6 7 + 2 + - - + 2 + 3 | | 3 + - - - - + + 4 | | 4 | | + 5 | | 5 | | + 6 | | 6 + - - - - + + 7 + - - + 7 + */ + CGRect innerClipping1 = CGRectMake(3, 2, 4, 6); + CGRect innerClipping2 = CGRectMake(2, 3, 6, 4); + CGRect outterClipping = CGRectMake(2, 2, 6, 6); + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + CGPoint point = CGPointMake(i, j); + int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView]; + if (CGRectContainsPoint(innerClipping1, point) || + CGRectContainsPoint(innerClipping2, point)) { + // Pixels inside either of the 2 inner clippings should be fully opaque. + XCTAssertEqual(alpha, 255); + } else if (CGRectContainsPoint(outterClipping, point)) { + // Corner pixels (i.e. (2, 2), (2, 7), (7, 2) and (7, 7)) should be partially transparent. + XCTAssert(0 < alpha && alpha < 255); + } else { + // Pixels outside outterClipping should be fully transparent. + XCTAssertEqual(alpha, 0); + } + } + } +} + +- (void)testClipPath { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // Create embedded view params + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack + SkMatrix screenScaleMatrix = + SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); + stack.PushTransform(screenScaleMatrix); + // Push a clip path + SkPath path; + path.addRoundRect(SkRect::MakeXYWH(2, 2, 6, 6), 1, 1); + stack.PushClipPath(path); + + auto embeddedViewParams = + std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + gMockPlatformView.backgroundColor = UIColor.redColor; + XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); + ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; + [flutterView addSubview:childClippingView]; + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + /* + ClippingMask outterClipping + 2 3 4 5 6 7 2 3 4 5 6 7 + 2 / - - - - \ 2 + - - - - + + 3 | | 3 | | + 4 | | 4 | | + 5 | | 5 | | + 6 | | 6 | | + 7 \ - - - - / 7 + - - - - + + + innerClipping1 innerClipping2 + 2 3 4 5 6 7 2 3 4 5 6 7 + 2 + - - + 2 + 3 | | 3 + - - - - + + 4 | | 4 | | + 5 | | 5 | | + 6 | | 6 + - - - - + + 7 + - - + 7 + */ + CGRect innerClipping1 = CGRectMake(3, 2, 4, 6); + CGRect innerClipping2 = CGRectMake(2, 3, 6, 4); + CGRect outterClipping = CGRectMake(2, 2, 6, 6); + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + CGPoint point = CGPointMake(i, j); + int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView]; + if (CGRectContainsPoint(innerClipping1, point) || + CGRectContainsPoint(innerClipping2, point)) { + // Pixels inside either of the 2 inner clippings should be fully opaque. + XCTAssertEqual(alpha, 255); + } else if (CGRectContainsPoint(outterClipping, point)) { + // Corner pixels (i.e. (2, 2), (2, 7), (7, 2) and (7, 7)) should be partially transparent. + XCTAssert(0 < alpha && alpha < 255); + } else { + // Pixels outside outterClipping should be fully transparent. + XCTAssertEqual(alpha, 0); + } + } + } +} + +- (void)testSetFlutterViewControllerAfterCreateCanStillDispatchTouchEvents { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + // Find touch inteceptor view + UIView* touchInteceptorView = gMockPlatformView; + while (touchInteceptorView != nil && + ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) { + touchInteceptorView = touchInteceptorView.superview; + } + XCTAssertNotNil(touchInteceptorView); + + // Find ForwardGestureRecognizer + UIGestureRecognizer* forwardGectureRecognizer = nil; + for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) { + if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) { + forwardGectureRecognizer = gestureRecognizer; + break; + } + } + + // Before setting flutter view controller, events are not dispatched. + NSSet* touches1 = [[NSSet alloc] init]; + id event1 = OCMClassMock([UIEvent class]); + id flutterViewContoller = OCMClassMock([FlutterViewController class]); + [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1]; + OCMReject([flutterViewContoller touchesBegan:touches1 withEvent:event1]); + + // Set flutter view controller allows events to be dispatched. + NSSet* touches2 = [[NSSet alloc] init]; + id event2 = OCMClassMock([UIEvent class]); + flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller); + [forwardGectureRecognizer touchesBegan:touches2 withEvent:event2]; + OCMVerify([flutterViewContoller touchesBegan:touches2 withEvent:event2]); +} + +- (void)testSetFlutterViewControllerInTheMiddleOfTouchEventShouldStillAllowGesturesToBeHandled { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + // Find touch inteceptor view + UIView* touchInteceptorView = gMockPlatformView; + while (touchInteceptorView != nil && + ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) { + touchInteceptorView = touchInteceptorView.superview; + } + XCTAssertNotNil(touchInteceptorView); + + // Find ForwardGestureRecognizer + UIGestureRecognizer* forwardGectureRecognizer = nil; + for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) { + if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) { + forwardGectureRecognizer = gestureRecognizer; + break; + } + } + id flutterViewContoller = OCMClassMock([FlutterViewController class]); + { + // ***** Sequence 1, finishing touch event with touchEnded ***** // + flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller); + + NSSet* touches1 = [[NSSet alloc] init]; + id event1 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1]; + OCMVerify([flutterViewContoller touchesBegan:touches1 withEvent:event1]); + + flutterPlatformViewsController->SetFlutterViewController(nil); + + // Allow the touch events to finish + NSSet* touches2 = [[NSSet alloc] init]; + id event2 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesMoved:touches2 withEvent:event2]; + OCMVerify([flutterViewContoller touchesMoved:touches2 withEvent:event2]); + + NSSet* touches3 = [[NSSet alloc] init]; + id event3 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesEnded:touches3 withEvent:event3]; + OCMVerify([flutterViewContoller touchesEnded:touches3 withEvent:event3]); + + // Now the 2nd touch sequence should not be allowed. + NSSet* touches4 = [[NSSet alloc] init]; + id event4 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesBegan:touches4 withEvent:event4]; + OCMReject([flutterViewContoller touchesBegan:touches4 withEvent:event4]); + + NSSet* touches5 = [[NSSet alloc] init]; + id event5 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5]; + OCMReject([flutterViewContoller touchesEnded:touches5 withEvent:event5]); + } + + { + // ***** Sequence 2, finishing touch event with touchCancelled ***** // + flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller); + + NSSet* touches1 = [[NSSet alloc] init]; + id event1 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1]; + OCMVerify([flutterViewContoller touchesBegan:touches1 withEvent:event1]); + + flutterPlatformViewsController->SetFlutterViewController(nil); + + // Allow the touch events to finish + NSSet* touches2 = [[NSSet alloc] init]; + id event2 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesMoved:touches2 withEvent:event2]; + OCMVerify([flutterViewContoller touchesMoved:touches2 withEvent:event2]); + + NSSet* touches3 = [[NSSet alloc] init]; + id event3 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesCancelled:touches3 withEvent:event3]; + OCMVerify([flutterViewContoller forceTouchesCancelled:touches3]); + + // Now the 2nd touch sequence should not be allowed. + NSSet* touches4 = [[NSSet alloc] init]; + id event4 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesBegan:touches4 withEvent:event4]; + OCMReject([flutterViewContoller touchesBegan:touches4 withEvent:event4]); + + NSSet* touches5 = [[NSSet alloc] init]; + id event5 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5]; + OCMReject([flutterViewContoller touchesEnded:touches5 withEvent:event5]); + } + + flutterPlatformViewsController->Reset(); +} + +- (void) + testSetFlutterViewControllerInTheMiddleOfTouchEventAllowsTheNewControllerToHandleSecondTouchSequence { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + // Find touch inteceptor view + UIView* touchInteceptorView = gMockPlatformView; + while (touchInteceptorView != nil && + ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) { + touchInteceptorView = touchInteceptorView.superview; + } + XCTAssertNotNil(touchInteceptorView); + + // Find ForwardGestureRecognizer + UIGestureRecognizer* forwardGectureRecognizer = nil; + for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) { + if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) { + forwardGectureRecognizer = gestureRecognizer; + break; + } + } + id flutterViewContoller = OCMClassMock([FlutterViewController class]); + + flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller); + + // The touches in this sequence requires 1 touch object, we always create the NSSet with one item. + NSSet* touches1 = [NSSet setWithObject:@1]; + id event1 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1]; + OCMVerify([flutterViewContoller touchesBegan:touches1 withEvent:event1]); + + FlutterViewController* flutterViewContoller2 = OCMClassMock([FlutterViewController class]); + flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller2); + + // Touch events should still send to the old FlutterViewController if FlutterViewController + // is updated in between. + NSSet* touches2 = [NSSet setWithObject:@1]; + id event2 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesBegan:touches2 withEvent:event2]; + OCMVerify([flutterViewContoller touchesBegan:touches2 withEvent:event2]); + OCMReject([flutterViewContoller2 touchesBegan:touches2 withEvent:event2]); + + NSSet* touches3 = [NSSet setWithObject:@1]; + id event3 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesMoved:touches3 withEvent:event3]; + OCMVerify([flutterViewContoller touchesMoved:touches3 withEvent:event3]); + OCMReject([flutterViewContoller2 touchesMoved:touches3 withEvent:event3]); + + NSSet* touches4 = [NSSet setWithObject:@1]; + id event4 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesEnded:touches4 withEvent:event4]; + OCMVerify([flutterViewContoller touchesEnded:touches4 withEvent:event4]); + OCMReject([flutterViewContoller2 touchesEnded:touches4 withEvent:event4]); + + NSSet* touches5 = [NSSet setWithObject:@1]; + id event5 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5]; + OCMVerify([flutterViewContoller touchesEnded:touches5 withEvent:event5]); + OCMReject([flutterViewContoller2 touchesEnded:touches5 withEvent:event5]); + + // Now the 2nd touch sequence should go to the new FlutterViewController + + NSSet* touches6 = [NSSet setWithObject:@1]; + id event6 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesBegan:touches6 withEvent:event6]; + OCMVerify([flutterViewContoller2 touchesBegan:touches6 withEvent:event6]); + OCMReject([flutterViewContoller touchesBegan:touches6 withEvent:event6]); + + // Allow the touch events to finish + NSSet* touches7 = [NSSet setWithObject:@1]; + id event7 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesMoved:touches7 withEvent:event7]; + OCMVerify([flutterViewContoller2 touchesMoved:touches7 withEvent:event7]); + OCMReject([flutterViewContoller touchesMoved:touches7 withEvent:event7]); + + NSSet* touches8 = [NSSet setWithObject:@1]; + id event8 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesEnded:touches8 withEvent:event8]; + OCMVerify([flutterViewContoller2 touchesEnded:touches8 withEvent:event8]); + OCMReject([flutterViewContoller touchesEnded:touches8 withEvent:event8]); + + flutterPlatformViewsController->Reset(); +} + +- (void)testFlutterPlatformViewTouchesCancelledEventAreForcedToBeCancelled { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + // Find touch inteceptor view + UIView* touchInteceptorView = gMockPlatformView; + while (touchInteceptorView != nil && + ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) { + touchInteceptorView = touchInteceptorView.superview; + } + XCTAssertNotNil(touchInteceptorView); + + // Find ForwardGestureRecognizer + UIGestureRecognizer* forwardGectureRecognizer = nil; + for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) { + if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) { + forwardGectureRecognizer = gestureRecognizer; + break; + } + } + id flutterViewContoller = OCMClassMock([FlutterViewController class]); + + flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller); + + NSSet* touches1 = [NSSet setWithObject:@1]; + id event1 = OCMClassMock([UIEvent class]); + [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1]; + + [forwardGectureRecognizer touchesCancelled:touches1 withEvent:event1]; + OCMVerify([flutterViewContoller forceTouchesCancelled:touches1]); + + flutterPlatformViewsController->Reset(); +} + +- (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashing { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + + // Create embedded view params + flutter::MutatorsStack stack; + SkMatrix finalMatrix; + + auto embeddedViewParams_1 = + std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams_1)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + flutter::SurfaceFrame::FramebufferInfo framebuffer_info; + auto mock_surface = std::make_unique( + nullptr, framebuffer_info, + [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return false; }, + [](const flutter::SurfaceFrame& surface_frame) { return true; }, + /*frame_size=*/SkISize::Make(800, 600)); + XCTAssertFalse( + flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); + + auto embeddedViewParams_2 = + std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams_2)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + auto mock_surface_submit_true = std::make_unique( + nullptr, framebuffer_info, + [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, + [](const flutter::SurfaceFrame& surface_frame) { return true; }, + /*frame_size=*/SkISize::Make(800, 600)); + XCTAssertTrue(flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, + std::move(mock_surface_submit_true))); +} + +- (void) + testFlutterPlatformViewControllerResetDeallocsPlatformViewWhenRootViewsNotBindedToFlutterView { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + // autorelease pool to trigger an autorelease for all the root_views_ and touch_interceptors_. + @autoreleasepool { + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + flutter::MutatorsStack stack; + SkMatrix finalMatrix; + auto embeddedViewParams = + std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + + // Not calling |flutterPlatformViewsController::SubmitFrame| so that the platform views are not + // added to flutter_view_. + + XCTAssertNotNil(gMockPlatformView); + flutterPlatformViewsController->Reset(); + } + XCTAssertNil(gMockPlatformView); +} + +- (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}], + result); + + // First frame, |EmbeddedViewCount| 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->CompositeWithParams( + 0, flutterPlatformViewsController->GetCompositionParams(0)); + + XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 1UL); + + // Second frame, |EmbeddedViewCount| should be empty at the start + flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); + XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 0UL); + + auto embeddedViewParams2 = + std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); + flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams2)); + flutterPlatformViewsController->CompositeWithParams( + 0, flutterPlatformViewsController->GetCompositionParams(0)); + + XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 1UL); +} + +- (void) + testFlutterPlatformViewControllerSubmitFrameShouldOrderSubviewsCorrectlyWithDifferentViewHierarchy { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}], + result); + UIView* view1 = gMockPlatformView; + + // This overwrites `gMockPlatformView` to another view. + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}], + result); + UIView* view2 = gMockPlatformView; + + 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)); + + auto embeddedViewParams2 = + std::make_unique(finalMatrix, SkSize::Make(500, 500), stack); + flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams2)); + + // SKSurface is required if the root FlutterView is present. + const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000); + sk_sp mock_sk_surface = SkSurfaces::Raster(image_info); + flutter::SurfaceFrame::FramebufferInfo framebuffer_info; + auto mock_surface = std::make_unique( + std::move(mock_sk_surface), framebuffer_info, + [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, + [](const flutter::SurfaceFrame& surface_frame) { return true; }, + /*frame_size=*/SkISize::Make(800, 600)); + + XCTAssertTrue( + flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); + + // platform view is wrapped by touch interceptor, which itself is wrapped by clipping view. + UIView* clippingView1 = view1.superview.superview; + UIView* clippingView2 = view2.superview.superview; + XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] < + [flutterView.subviews indexOfObject:clippingView2], + @"The first clipping view should be added before the second clipping view."); + + // Need to recreate these params since they are `std::move`ed. + flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); + // Process the second frame in the opposite order. + embeddedViewParams2 = + std::make_unique(finalMatrix, SkSize::Make(500, 500), stack); + flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams2)); + + embeddedViewParams1 = + std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); + flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams1)); + + mock_sk_surface = SkSurfaces::Raster(image_info); + mock_surface = std::make_unique( + std::move(mock_sk_surface), framebuffer_info, + [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, + [](const flutter::SurfaceFrame& surface_frame) { return true; }, + /*frame_size=*/SkISize::Make(800, 600)); + XCTAssertTrue( + flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); + + XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] > + [flutterView.subviews indexOfObject:clippingView2], + @"The first clipping view should be added after the second clipping view."); +} + +- (void) + testFlutterPlatformViewControllerSubmitFrameShouldOrderSubviewsCorrectlyWithSameViewHierarchy { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}], + result); + UIView* view1 = gMockPlatformView; + + // This overwrites `gMockPlatformView` to another view. + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}], + result); + UIView* view2 = gMockPlatformView; + + 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)); + + auto embeddedViewParams2 = + std::make_unique(finalMatrix, SkSize::Make(500, 500), stack); + flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams2)); + + // SKSurface is required if the root FlutterView is present. + const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000); + sk_sp mock_sk_surface = SkSurfaces::Raster(image_info); + flutter::SurfaceFrame::FramebufferInfo framebuffer_info; + auto mock_surface = std::make_unique( + std::move(mock_sk_surface), framebuffer_info, + [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, + [](const flutter::SurfaceFrame& surface_frame) { return true; }, + /*frame_size=*/SkISize::Make(800, 600)); + + XCTAssertTrue( + flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); + + // platform view is wrapped by touch interceptor, which itself is wrapped by clipping view. + UIView* clippingView1 = view1.superview.superview; + UIView* clippingView2 = view2.superview.superview; + XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] < + [flutterView.subviews indexOfObject:clippingView2], + @"The first clipping view should be added before the second clipping view."); + + // Need to recreate these params since they are `std::move`ed. + flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); + // Process the second frame in the same order. + embeddedViewParams1 = + std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); + flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams1)); + + embeddedViewParams2 = + std::make_unique(finalMatrix, SkSize::Make(500, 500), stack); + flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams2)); + + mock_sk_surface = SkSurfaces::Raster(image_info); + mock_surface = std::make_unique( + std::move(mock_sk_surface), framebuffer_info, + [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, + [](const flutter::SurfaceFrame& surface_frame) { return true; }, + /*frame_size=*/SkISize::Make(800, 600)); + XCTAssertTrue( + flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); + + XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] < + [flutterView.subviews indexOfObject:clippingView2], + @"The first clipping view should be added before the second clipping view."); +} + +- (int)alphaOfPoint:(CGPoint)point onView:(UIView*)view { + unsigned char pixel[4] = {0}; + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + // Draw the pixel on `point` in the context. + CGContextRef context = CGBitmapContextCreate( + pixel, 1, 1, 8, 4, colorSpace, kCGBitmapAlphaInfoMask & kCGImageAlphaPremultipliedLast); + CGContextTranslateCTM(context, -point.x, -point.y); + [view.layer renderInContext:context]; + + CGContextRelease(context); + CGColorSpaceRelease(colorSpace); + // Get the alpha from the pixel that we just rendered. + return pixel[3]; +} + +- (void)testHasFirstResponderInViewHierarchySubtree_viewItselfBecomesFirstResponder { + // For view to become the first responder, it must be a descendant of a UIWindow + UIWindow* window = [[UIWindow alloc] init]; + UITextField* textField = [[UITextField alloc] init]; + [window addSubview:textField]; + + [textField becomeFirstResponder]; + XCTAssertTrue(textField.isFirstResponder); + XCTAssertTrue(textField.flt_hasFirstResponderInViewHierarchySubtree); + [textField resignFirstResponder]; + XCTAssertFalse(textField.isFirstResponder); + XCTAssertFalse(textField.flt_hasFirstResponderInViewHierarchySubtree); +} + +- (void)testHasFirstResponderInViewHierarchySubtree_descendantViewBecomesFirstResponder { + // For view to become the first responder, it must be a descendant of a UIWindow + UIWindow* window = [[UIWindow alloc] init]; + UIView* view = [[UIView alloc] init]; + UIView* childView = [[UIView alloc] init]; + UITextField* textField = [[UITextField alloc] init]; + [window addSubview:view]; + [view addSubview:childView]; + [childView addSubview:textField]; + + [textField becomeFirstResponder]; + XCTAssertTrue(textField.isFirstResponder); + XCTAssertTrue(view.flt_hasFirstResponderInViewHierarchySubtree); + [textField resignFirstResponder]; + XCTAssertFalse(textField.isFirstResponder); + XCTAssertFalse(view.flt_hasFirstResponderInViewHierarchySubtree); +} + +- (void)testFlutterClippingMaskViewPoolReuseViewsAfterRecycle { + FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2]; + FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero]; + FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero]; + [pool insertViewToPoolIfNeeded:view1]; + [pool insertViewToPoolIfNeeded:view2]; + CGRect newRect = CGRectMake(0, 0, 10, 10); + FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:newRect]; + FlutterClippingMaskView* view4 = [pool getMaskViewWithFrame:newRect]; + // view3 and view4 should randomly get either of view1 and view2. + NSSet* set1 = [NSSet setWithObjects:view1, view2, nil]; + NSSet* set2 = [NSSet setWithObjects:view3, view4, nil]; + XCTAssertEqualObjects(set1, set2); + XCTAssertTrue(CGRectEqualToRect(view3.frame, newRect)); + XCTAssertTrue(CGRectEqualToRect(view4.frame, newRect)); +} + +- (void)testFlutterClippingMaskViewPoolAllocsNewMaskViewsAfterReachingCapacity { + FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2]; + FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero]; + FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero]; + FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:CGRectZero]; + XCTAssertNotEqual(view1, view3); + XCTAssertNotEqual(view2, view3); +} + +- (void)testMaskViewsReleasedWhenPoolIsReleased { + __weak UIView* weakView; + @autoreleasepool { + FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2]; + FlutterClippingMaskView* view = [pool getMaskViewWithFrame:CGRectZero]; + weakView = view; + XCTAssertNotNil(weakView); + } + XCTAssertNil(weakView); +} + +- (void)testClipMaskViewIsReused { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // 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( + screenScaleMatrix, SkSize::Make(10, 10), stack1); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1)); + flutterPlatformViewsController->CompositeWithParams( + 1, flutterPlatformViewsController->GetCompositionParams(1)); + + UIView* childClippingView1 = gMockPlatformView.superview.superview; + UIView* maskView1 = childClippingView1.maskView; + XCTAssertNotNil(maskView1); + + // Composite a new frame. + flutterPlatformViewsController->BeginFrame(SkISize::Make(100, 100)); + flutter::MutatorsStack stack2; + auto embeddedViewParams2 = std::make_unique( + screenScaleMatrix, SkSize::Make(10, 10), stack2); + auto embeddedViewParams3 = std::make_unique( + screenScaleMatrix, SkSize::Make(10, 10), stack2); + flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams3)); + flutterPlatformViewsController->CompositeWithParams( + 1, flutterPlatformViewsController->GetCompositionParams(1)); + + childClippingView1 = gMockPlatformView.superview.superview; + + // This overrides gMockPlatformView to point to the newly created platform view. + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + + auto embeddedViewParams4 = std::make_unique( + screenScaleMatrix, SkSize::Make(10, 10), stack1); + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams4)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + UIView* childClippingView2 = gMockPlatformView.superview.superview; + + UIView* maskView2 = childClippingView2.maskView; + XCTAssertEqual(maskView1, maskView2); + XCTAssertNotNil(childClippingView2.maskView); + XCTAssertNil(childClippingView1.maskView); +} + +- (void)testDifferentClipMaskViewIsUsedForEachView { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + 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* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // 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( + screenScaleMatrix, SkSize::Make(10, 10), stack1); + + flutter::MutatorsStack stack2; + stack2.PushClipRect(rect); + auto embeddedViewParams2 = std::make_unique( + screenScaleMatrix, SkSize::Make(10, 10), stack2); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1)); + flutterPlatformViewsController->CompositeWithParams( + 1, flutterPlatformViewsController->GetCompositionParams(1)); + + UIView* childClippingView1 = view1.superview.superview; + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams2)); + flutterPlatformViewsController->CompositeWithParams( + 2, flutterPlatformViewsController->GetCompositionParams(2)); + + UIView* childClippingView2 = view2.superview.superview; + UIView* maskView1 = childClippingView1.maskView; + UIView* maskView2 = childClippingView2.maskView; + XCTAssertNotEqual(maskView1, maskView2); +} + +- (void)testMaskViewUsesCAShapeLayerAsTheBackingLayer { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}], + result); + + XCTAssertNotNil(gMockPlatformView); + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // 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( + screenScaleMatrix, SkSize::Make(10, 10), stack1); + + flutter::MutatorsStack stack2; + stack2.PushClipRect(rect); + auto embeddedViewParams2 = std::make_unique( + screenScaleMatrix, SkSize::Make(10, 10), stack2); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1)); + flutterPlatformViewsController->CompositeWithParams( + 1, flutterPlatformViewsController->GetCompositionParams(1)); + + UIView* childClippingView = gMockPlatformView.superview.superview; + + UIView* maskView = childClippingView.maskView; + XCTAssert([maskView.layer isKindOfClass:[CAShapeLayer class]], + @"Mask view must use CAShapeLayer as its backing layer."); +} + +// Return true if a correct visual effect view is found. It also implies all the validation in this +// method passes. +// +// There are two fail states for this method. 1. One of the XCTAssert method failed; or 2. No +// correct visual effect view found. +- (BOOL)validateOneVisualEffectView:(UIView*)visualEffectView + expectedFrame:(CGRect)frame + inputRadius:(CGFloat)inputRadius { + XCTAssertTrue(CGRectEqualToRect(visualEffectView.frame, frame)); + for (UIView* view in visualEffectView.subviews) { + if (![NSStringFromClass([view class]) hasSuffix:@"BackdropView"]) { + continue; + } + XCTAssertEqual(view.layer.filters.count, 1u); + NSObject* filter = view.layer.filters.firstObject; + + XCTAssertEqualObjects([filter valueForKey:@"name"], @"gaussianBlur"); + + NSObject* inputRadiusInFilter = [filter valueForKey:@"inputRadius"]; + XCTAssertTrue([inputRadiusInFilter isKindOfClass:[NSNumber class]] && + flutter::BlurRadiusEqualToBlurRadius(((NSNumber*)inputRadiusInFilter).floatValue, + inputRadius)); + return YES; + } + return NO; +} + +- (void)testDisposingViewInCompositionOrderDoNotCrash { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}], + result); + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}], + result); + + { + // **** First frame, view id 0, 1 in the composition_order_, disposing view 0 is called. **** // + // No view should be disposed, or removed from the composition order. + flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); + flutter::MutatorsStack stack; + SkMatrix finalMatrix; + auto embeddedViewParams0 = + std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); + flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams0)); + + auto embeddedViewParams1 = + std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); + flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1)); + + XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 2UL); + + XCTestExpectation* expectation = [self expectationWithDescription:@"dispose call ended."]; + FlutterResult disposeResult = ^(id result) { + [expectation fulfill]; + }; + + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall methodCallWithMethodName:@"dispose" arguments:@0], disposeResult); + [self waitForExpectationsWithTimeout:30 handler:nil]; + + const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000); + sk_sp mock_sk_surface = SkSurfaces::Raster(image_info); + flutter::SurfaceFrame::FramebufferInfo framebuffer_info; + auto mock_surface = std::make_unique( + std::move(mock_sk_surface), framebuffer_info, + [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, + [](const flutter::SurfaceFrame& surface_frame) { return true; }, + /*frame_size=*/SkISize::Make(800, 600)); + XCTAssertTrue( + flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); + + // Disposing won't remove embedded views until the view is removed from the composition_order_ + XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 2UL); + XCTAssertNotNil(flutterPlatformViewsController->GetPlatformViewByID(0)); + XCTAssertNotNil(flutterPlatformViewsController->GetPlatformViewByID(1)); + } + + { + // **** Second frame, view id 1 in the composition_order_, no disposing view is called, **** // + // View 0 is removed from the composition order in this frame, hence also disposed. + flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); + flutter::MutatorsStack stack; + SkMatrix finalMatrix; + auto embeddedViewParams1 = + std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); + flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1)); + + const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000); + sk_sp mock_sk_surface = SkSurfaces::Raster(image_info); + flutter::SurfaceFrame::FramebufferInfo framebuffer_info; + auto mock_surface = std::make_unique( + std::move(mock_sk_surface), framebuffer_info, + [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, + [](const flutter::SurfaceFrame& surface_frame) { return true; }, + /*frame_size=*/SkISize::Make(800, 600)); + XCTAssertTrue( + flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); + + // Disposing won't remove embedded views until the view is removed from the composition_order_ + XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 1UL); + XCTAssertNil(flutterPlatformViewsController->GetPlatformViewByID(0)); + XCTAssertNotNil(flutterPlatformViewsController->GetPlatformViewByID(1)); + } +} +- (void)testOnlyPlatformViewsAreRemovedWhenReset { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + auto flutterPlatformViewsController = std::make_shared(); + flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + flutterPlatformViewsController->RegisterViewFactory( + factory, @"MockFlutterPlatformView", + FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + FlutterResult result = ^(id result) { + }; + flutterPlatformViewsController->OnMethodCall( + [FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], + result); + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; + flutterPlatformViewsController->SetFlutterView(flutterView); + // Create embedded view params + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack + SkMatrix screenScaleMatrix = + SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); + stack.PushTransform(screenScaleMatrix); + // Push a translate matrix + SkMatrix translateMatrix = SkMatrix::Translate(100, 100); + stack.PushTransform(translateMatrix); + SkMatrix finalMatrix; + finalMatrix.setConcat(screenScaleMatrix, translateMatrix); + + auto embeddedViewParams = + std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + + // SKSurface is required if the root FlutterView is present. + const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000); + sk_sp mock_sk_surface = SkSurfaces::Raster(image_info); + flutter::SurfaceFrame::FramebufferInfo framebuffer_info; + auto mock_surface = std::make_unique( + std::move(mock_sk_surface), framebuffer_info, + [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, + [](const flutter::SurfaceFrame& surface_frame) { return true; }, + /*frame_size=*/SkISize::Make(800, 600)); + + flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface)); + + UIView* someView = [[UIView alloc] init]; + [flutterView addSubview:someView]; + + flutterPlatformViewsController->Reset(); + XCTAssertEqual(flutterView.subviews.count, 1u); + XCTAssertEqual(flutterView.subviews.firstObject, someView); +} + +- (void)testFlutterTouchInterceptingViewLinksToAccessibilityContainer { + FlutterTouchInterceptingView* touchInteceptorView = [[FlutterTouchInterceptingView alloc] init]; + NSObject* container = [[NSObject alloc] init]; + [touchInteceptorView setFlutterAccessibilityContainer:container]; + XCTAssertEqualObjects([touchInteceptorView accessibilityContainer], container); +} + +- (void)testLayerPool { + // Create an IOSContext and GrDirectContext. + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"]; + [engine run]; + XCTAssertTrue([engine iosPlatformView] != nullptr); + auto ios_context = [engine iosPlatformView]->GetIosContext(); + auto gr_context = ios_context->GetMainContext(); + + auto pool = flutter::OverlayLayerPool{}; + + // Add layers to the pool. + pool.CreateLayer(gr_context.get(), ios_context, MTLPixelFormatBGRA8Unorm); + XCTAssertEqual(pool.size(), 1u); + pool.CreateLayer(gr_context.get(), ios_context, MTLPixelFormatBGRA8Unorm); + XCTAssertEqual(pool.size(), 2u); + + // Mark all layers as unused. + pool.RecycleLayers(); + XCTAssertEqual(pool.size(), 2u); + + // Free the unused layers. One should remain. + auto unused_layers = pool.RemoveUnusedLayers(); + XCTAssertEqual(unused_layers.size(), 2u); + XCTAssertEqual(pool.size(), 1u); +} + +@end diff --git a/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.h b/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.h new file mode 100644 index 0000000000000..bd04aa81a8bf5 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.h @@ -0,0 +1,102 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_OVERLAY_LAYER_POOL_H_ +#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_OVERLAY_LAYER_POOL_H_ + +#include +#include + +#import + +#include "flow/surface.h" +#include "fml/platform/darwin/scoped_nsobject.h" + +#import "flutter/shell/platform/darwin/ios/ios_context.h" + +namespace flutter { + +class IOSSurface; + +/// @brief State holder for a Flutter overlay layer. +struct OverlayLayer { + OverlayLayer(const fml::scoped_nsobject& overlay_view, + const fml::scoped_nsobject& overlay_view_wrapper, + std::unique_ptr ios_surface, + std::unique_ptr surface); + + ~OverlayLayer() = default; + + fml::scoped_nsobject overlay_view; + fml::scoped_nsobject overlay_view_wrapper; + std::unique_ptr ios_surface; + std::unique_ptr surface; + + // Whether a frame for this layer was submitted. + bool did_submit_last_frame; + + // The GrContext that is currently used by the overlay surfaces. + // We track this to know when the GrContext for the Flutter app has changed + // so we can update the overlay with the new context. + GrDirectContext* gr_context; + + void UpdateViewState(UIView* flutter_view, SkRect rect, int64_t view_id, int64_t overlay_id); +}; + +/// @brief Storage for Overlay layers across frames. +/// +/// Note: this class does not synchronize access to its layers or any layer removal. As it +/// is currently used, layers must be created on the platform thread but other methods of +/// it are called on the raster thread. This is safe as overlay layers are only ever added +/// while the raster thread is latched. +class OverlayLayerPool { + public: + OverlayLayerPool() = default; + + ~OverlayLayerPool() = default; + + /// @brief Gets a layer from the pool if available. + /// + /// The layer is marked as used until [RecycleLayers] is called. + std::shared_ptr GetNextLayer(); + + /// @brief Create a new overlay layer. + /// + /// This method can only be called on the Platform thread. + void CreateLayer(GrDirectContext* gr_context, + const std::shared_ptr& ios_context, + MTLPixelFormat pixel_format); + + /// @brief Removes unused layers from the pool. Returns the unused layers. + std::vector> RemoveUnusedLayers(); + + /// @brief Marks the layers in the pool as available for reuse. + void RecycleLayers(); + + /// @brief The count of layers currently in the pool. + size_t size() const; + + private: + OverlayLayerPool(const OverlayLayerPool&) = delete; + OverlayLayerPool& operator=(const OverlayLayerPool&) = delete; + + // The index of the entry in the layers_ vector that determines the beginning of the unused + // layers. For example, consider the following vector: + // _____ + // | 0 | + /// |---| + /// | 1 | <-- available_layer_index_ + /// |---| + /// | 2 | + /// |---| + /// + /// This indicates that entries starting from 1 can be reused meanwhile the entry at position 0 + /// cannot be reused. + size_t available_layer_index_ = 0; + std::vector> layers_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_OVERLAY_LAYER_POOL_H_ diff --git a/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.mm b/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.mm new file mode 100644 index 0000000000000..4d63a595fcc4f --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.mm @@ -0,0 +1,136 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.h" + +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h" +#import "flutter/shell/platform/darwin/ios/ios_surface.h" + +namespace flutter { + +OverlayLayer::OverlayLayer(const fml::scoped_nsobject& overlay_view, + const fml::scoped_nsobject& overlay_view_wrapper, + std::unique_ptr ios_surface, + std::unique_ptr surface) + : overlay_view(overlay_view), + overlay_view_wrapper(overlay_view_wrapper), + ios_surface(std::move(ios_surface)), + surface(std::move(surface)){}; + +void OverlayLayer::UpdateViewState(UIView* flutter_view, + SkRect rect, + int64_t view_id, + int64_t overlay_id) { + UIView* overlay_view_wrapper = this->overlay_view_wrapper.get(); + auto screenScale = [UIScreen mainScreen].scale; + // Set the size of the overlay view wrapper. + // This wrapper view masks the overlay view. + overlay_view_wrapper.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale, + rect.width() / screenScale, rect.height() / screenScale); + // Set a unique view identifier, so the overlay_view_wrapper can be identified in XCUITests. + overlay_view_wrapper.accessibilityIdentifier = + [NSString stringWithFormat:@"platform_view[%lld].overlay[%lld]", view_id, overlay_id]; + + UIView* overlay_view = this->overlay_view.get(); + // Set the size of the overlay view. + // This size is equal to the device screen size. + overlay_view.frame = [flutter_view convertRect:flutter_view.bounds toView:overlay_view_wrapper]; + // Set a unique view identifier, so the overlay_view can be identified in XCUITests. + overlay_view.accessibilityIdentifier = + [NSString stringWithFormat:@"platform_view[%lld].overlay_view[%lld]", view_id, overlay_id]; +} + +// OverlayLayerPool +//////////////////////////////////////////////////////// + +std::shared_ptr OverlayLayerPool::GetNextLayer() { + std::shared_ptr result; + if (available_layer_index_ < layers_.size()) { + result = layers_[available_layer_index_]; + available_layer_index_++; + } + + return result; +} + +void OverlayLayerPool::CreateLayer(GrDirectContext* gr_context, + const std::shared_ptr& ios_context, + MTLPixelFormat pixel_format) { + FML_DCHECK([[NSThread currentThread] isMainThread]); + std::shared_ptr layer; + fml::scoped_nsobject overlay_view; + fml::scoped_nsobject overlay_view_wrapper; + + bool impeller_enabled = !!ios_context->GetImpellerContext(); + if (!gr_context && !impeller_enabled) { + overlay_view.reset([[FlutterOverlayView alloc] init]); + overlay_view_wrapper.reset([[FlutterOverlayView alloc] init]); + + auto ca_layer = fml::scoped_nsobject{[overlay_view.get() layer]}; + std::unique_ptr ios_surface = IOSSurface::Create(ios_context, ca_layer); + std::unique_ptr surface = ios_surface->CreateGPUSurface(); + + layer = std::make_shared(std::move(overlay_view), std::move(overlay_view_wrapper), + std::move(ios_surface), std::move(surface)); + } else { + CGFloat screenScale = [UIScreen mainScreen].scale; + overlay_view.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale + pixelFormat:pixel_format]); + overlay_view_wrapper.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale + pixelFormat:pixel_format]); + + auto ca_layer = fml::scoped_nsobject{[overlay_view.get() layer]}; + std::unique_ptr ios_surface = IOSSurface::Create(ios_context, ca_layer); + std::unique_ptr surface = ios_surface->CreateGPUSurface(gr_context); + + layer = std::make_shared(std::move(overlay_view), std::move(overlay_view_wrapper), + std::move(ios_surface), std::move(surface)); + layer->gr_context = gr_context; + } + // The overlay view wrapper masks the overlay view. + // This is required to keep the backing surface size unchanged between frames. + // + // Otherwise, changing the size of the overlay would require a new surface, + // which can be very expensive. + // + // This is the case of an animation in which the overlay size is changing in every frame. + // + // +------------------------+ + // | overlay_view | + // | +--------------+ | +--------------+ + // | | wrapper | | == mask => | overlay_view | + // | +--------------+ | +--------------+ + // +------------------------+ + layer->overlay_view_wrapper.get().clipsToBounds = YES; + [layer->overlay_view_wrapper.get() addSubview:layer->overlay_view]; + + layers_.push_back(layer); +} + +void OverlayLayerPool::RecycleLayers() { + available_layer_index_ = 0; +} + +std::vector> OverlayLayerPool::RemoveUnusedLayers() { + std::vector> results; + for (size_t i = available_layer_index_; i < layers_.size(); i++) { + results.push_back(layers_[i]); + } + // Leave at least one overlay layer, to work around cases where scrolling + // platform views under an app bar continually adds and removes an + // overlay layer. This logic could be removed if https://github.com/flutter/flutter/issues/150646 + // is fixed. + static constexpr size_t kLeakLayerCount = 1; + size_t erase_offset = std::max(available_layer_index_, kLeakLayerCount); + if (erase_offset < layers_.size()) { + layers_.erase(layers_.begin() + erase_offset, layers_.end()); + } + return results; +} + +size_t OverlayLayerPool::size() const { + return layers_.size(); +} + +} // namespace flutter diff --git a/shell/platform/darwin/ios/framework/Source/platform_views_controller.h b/shell/platform/darwin/ios/framework/Source/platform_views_controller.h index b98e4d8345660..551792ef8d47b 100644 --- a/shell/platform/darwin/ios/framework/Source/platform_views_controller.h +++ b/shell/platform/darwin/ios/framework/Source/platform_views_controller.h @@ -10,101 +10,23 @@ #include "flutter/flow/surface.h" #include "flutter/fml/memory/weak_ptr.h" #include "flutter/fml/platform/darwin/scoped_nsobject.h" +#include "flutter/fml/task_runner.h" #include "flutter/fml/trace_event.h" +#include "impeller/base/thread_safety.h" +#include "third_party/skia/include/core/SkRect.h" + #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.h" #import "flutter/shell/platform/darwin/ios/ios_context.h" -#include "fml/task_runner.h" -#include "impeller/base/thread_safety.h" -#include "third_party/skia/include/core/SkRect.h" @class FlutterTouchInterceptingView; @class FlutterClippingMaskViewPool; namespace flutter { -class IOSSurface; - -/// @brief State holder for a Flutter overlay layer. -struct OverlayLayer { - OverlayLayer(const fml::scoped_nsobject& overlay_view, - const fml::scoped_nsobject& overlay_view_wrapper, - std::unique_ptr ios_surface, - std::unique_ptr surface); - - ~OverlayLayer() = default; - - fml::scoped_nsobject overlay_view; - fml::scoped_nsobject overlay_view_wrapper; - std::unique_ptr ios_surface; - std::unique_ptr surface; - - // Whether a frame for this layer was submitted. - bool did_submit_last_frame; - - // The GrContext that is currently used by the overlay surfaces. - // We track this to know when the GrContext for the Flutter app has changed - // so we can update the overlay with the new context. - GrDirectContext* gr_context; - - void UpdateViewState(UIView* flutter_view, SkRect rect, int64_t view_id, int64_t overlay_id); -}; - -/// @brief Storage for Overlay layers across frames. -/// -/// Note: this class does not synchronize access to its layers or any layer removal. As it -/// is currently used, layers must be created on the platform thread but other methods of -/// it are called on the raster thread. This is safe as overlay layers are only ever added -/// while the raster thread is latched. -class OverlayLayerPool { - public: - OverlayLayerPool() = default; - - ~OverlayLayerPool() = default; - - /// @brief Gets a layer from the pool if available. - /// - /// The layer is marked as used until [RecycleLayers] is called. - std::shared_ptr GetNextLayer(); - - /// @brief Create a new overlay layer. - /// - /// This method can only be called on the Platform thread. - void CreateLayer(GrDirectContext* gr_context, - const std::shared_ptr& ios_context, - MTLPixelFormat pixel_format); - - /// @brief Removes unused layers from the pool. Returns the unused layers. - std::vector> RemoveUnusedLayers(); - - /// @brief Marks the layers in the pool as available for reuse. - void RecycleLayers(); - - /// @brief The count of layers currently in the pool. - size_t size() const; - - private: - OverlayLayerPool(const OverlayLayerPool&) = delete; - OverlayLayerPool& operator=(const OverlayLayerPool&) = delete; - - // The index of the entry in the layers_ vector that determines the beginning of the unused - // layers. For example, consider the following vector: - // _____ - // | 0 | - /// |---| - /// | 1 | <-- available_layer_index_ - /// |---| - /// | 2 | - /// |---| - /// - /// This indicates that entries starting from 1 can be reused meanwhile the entry at position 0 - /// cannot be reused. - size_t available_layer_index_ = 0; - std::vector> layers_; -}; - /// @brief Composites Flutter UI and overlay layers alongside embedded UIViews. class PlatformViewsController { public: diff --git a/shell/platform/darwin/ios/framework/Source/platform_views_controller.mm b/shell/platform/darwin/ios/framework/Source/platform_views_controller.mm index e840ba4e0c0e3..2627268961997 100644 --- a/shell/platform/darwin/ios/framework/Source/platform_views_controller.mm +++ b/shell/platform/darwin/ios/framework/Source/platform_views_controller.mm @@ -7,10 +7,11 @@ #include "flow/surface_frame.h" #include "flutter/flow/view_slicer.h" #include "flutter/fml/make_copyable.h" +#include "fml/synchronization/count_down_latch.h" + +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h" #import "flutter/shell/platform/darwin/ios/ios_surface.h" -#include "fml/synchronization/count_down_latch.h" -#include "shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h" namespace { @@ -111,110 +112,6 @@ bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, // Becomes NO if Apple's API changes and blurred backdrop filters cannot be applied. BOOL canApplyBlurBackdrop = YES; -OverlayLayer::OverlayLayer(const fml::scoped_nsobject& overlay_view, - const fml::scoped_nsobject& overlay_view_wrapper, - std::unique_ptr ios_surface, - std::unique_ptr surface) - : overlay_view(overlay_view), - overlay_view_wrapper(overlay_view_wrapper), - ios_surface(std::move(ios_surface)), - surface(std::move(surface)){}; - -// OverlayLayerPool -//////////////////////////////////////////////////////// - -std::shared_ptr OverlayLayerPool::GetNextLayer() { - std::shared_ptr result; - if (available_layer_index_ < layers_.size()) { - result = layers_[available_layer_index_]; - available_layer_index_++; - } - - return result; -} - -void OverlayLayerPool::CreateLayer(GrDirectContext* gr_context, - const std::shared_ptr& ios_context, - MTLPixelFormat pixel_format) { - FML_DCHECK([[NSThread currentThread] isMainThread]); - std::shared_ptr layer; - fml::scoped_nsobject overlay_view; - fml::scoped_nsobject overlay_view_wrapper; - - bool impeller_enabled = !!ios_context->GetImpellerContext(); - if (!gr_context && !impeller_enabled) { - overlay_view.reset([[FlutterOverlayView alloc] init]); - overlay_view_wrapper.reset([[FlutterOverlayView alloc] init]); - - auto ca_layer = fml::scoped_nsobject{[overlay_view.get() layer]}; - std::unique_ptr ios_surface = IOSSurface::Create(ios_context, ca_layer); - std::unique_ptr surface = ios_surface->CreateGPUSurface(); - - layer = std::make_shared(std::move(overlay_view), std::move(overlay_view_wrapper), - std::move(ios_surface), std::move(surface)); - } else { - CGFloat screenScale = [UIScreen mainScreen].scale; - overlay_view.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale - pixelFormat:pixel_format]); - overlay_view_wrapper.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale - pixelFormat:pixel_format]); - - auto ca_layer = fml::scoped_nsobject{[overlay_view.get() layer]}; - std::unique_ptr ios_surface = IOSSurface::Create(ios_context, ca_layer); - std::unique_ptr surface = ios_surface->CreateGPUSurface(gr_context); - - layer = std::make_shared(std::move(overlay_view), std::move(overlay_view_wrapper), - std::move(ios_surface), std::move(surface)); - layer->gr_context = gr_context; - } - // The overlay view wrapper masks the overlay view. - // This is required to keep the backing surface size unchanged between frames. - // - // Otherwise, changing the size of the overlay would require a new surface, - // which can be very expensive. - // - // This is the case of an animation in which the overlay size is changing in every frame. - // - // +------------------------+ - // | overlay_view | - // | +--------------+ | +--------------+ - // | | wrapper | | == mask => | overlay_view | - // | +--------------+ | +--------------+ - // +------------------------+ - layer->overlay_view_wrapper.get().clipsToBounds = YES; - [layer->overlay_view_wrapper.get() addSubview:layer->overlay_view]; - - layers_.push_back(layer); -} - -void OverlayLayerPool::RecycleLayers() { - available_layer_index_ = 0; -} - -std::vector> OverlayLayerPool::RemoveUnusedLayers() { - std::vector> results; - for (size_t i = available_layer_index_; i < layers_.size(); i++) { - results.push_back(layers_[i]); - } - // Leave at least one overlay layer, to work around cases where scrolling - // platform views under an app bar continually adds and removes an - // overlay layer. This logic could be removed if https://github.com/flutter/flutter/issues/150646 - // is fixed. - static constexpr size_t kLeakLayerCount = 1; - size_t erase_offset = std::max(available_layer_index_, kLeakLayerCount); - if (erase_offset < layers_.size()) { - layers_.erase(layers_.begin() + erase_offset, layers_.end()); - } - return results; -} - -size_t OverlayLayerPool::size() const { - return layers_.size(); -} - -// PlatformViewsController -/////////////////////////////////////////////////// - PlatformViewsController::PlatformViewsController() : layer_pool_(std::make_unique()), weak_factory_(std::make_unique>(this)) { @@ -926,29 +823,6 @@ bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, layer_pool_->CreateLayer(gr_context, ios_context, pixel_format); } -void OverlayLayer::UpdateViewState(UIView* flutter_view, - SkRect rect, - int64_t view_id, - int64_t overlay_id) { - UIView* overlay_view_wrapper = this->overlay_view_wrapper.get(); - auto screenScale = [UIScreen mainScreen].scale; - // Set the size of the overlay view wrapper. - // This wrapper view masks the overlay view. - overlay_view_wrapper.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale, - rect.width() / screenScale, rect.height() / screenScale); - // Set a unique view identifier, so the overlay_view_wrapper can be identified in XCUITests. - overlay_view_wrapper.accessibilityIdentifier = - [NSString stringWithFormat:@"platform_view[%lld].overlay[%lld]", view_id, overlay_id]; - - UIView* overlay_view = this->overlay_view.get(); - // Set the size of the overlay view. - // This size is equal to the device screen size. - overlay_view.frame = [flutter_view convertRect:flutter_view.bounds toView:overlay_view_wrapper]; - // Set a unique view identifier, so the overlay_view can be identified in XCUITests. - overlay_view.accessibilityIdentifier = - [NSString stringWithFormat:@"platform_view[%lld].overlay_view[%lld]", view_id, overlay_id]; -} - void PlatformViewsController::RemoveUnusedLayers( const std::vector>& unused_layers, const std::vector& composition_order) { From 25a7e63f1d1ea4197e7d86de5e16687bd441d6e2 Mon Sep 17 00:00:00 2001 From: jonahwilliams Date: Tue, 6 Aug 2024 11:24:35 -0700 Subject: [PATCH 4/6] ++ --- ci/licenses_golden/licenses_flutter | 4 + .../ios/framework/Source/PlatformViewsTest.mm | 3391 ----------------- 2 files changed, 4 insertions(+), 3391 deletions(-) delete mode 100644 shell/platform/darwin/ios/framework/Source/PlatformViewsTest.mm diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index f910b73639e6f..e10405e0f0dc5 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -43712,6 +43712,8 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/availability ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection_test.mm + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_views_controller.h + ../../../flutter/LICENSE @@ -46617,6 +46619,8 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/availability_v FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection_test.mm +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.h +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_views_controller.h diff --git a/shell/platform/darwin/ios/framework/Source/PlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/PlatformViewsTest.mm deleted file mode 100644 index 05e999777b097..0000000000000 --- a/shell/platform/darwin/ios/framework/Source/PlatformViewsTest.mm +++ /dev/null @@ -1,3391 +0,0 @@ -// Copyright 2013 The Flutter 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 -#import -#include "fml/synchronization/count_down_latch.h" -#include "shell/platform/darwin/ios/framework/Source/platform_views_controller.h" - -#import "flutter/fml/thread.h" -#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" -#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h" -#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" -#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" -#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h" -#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTouchInterceptingView_Test.h" -#import "flutter/shell/platform/darwin/ios/platform_view_ios.h" - -FLUTTER_ASSERT_ARC - -@class FlutterPlatformViewsTestMockPlatformView; -__weak static FlutterPlatformViewsTestMockPlatformView* gMockPlatformView = nil; -const float kFloatCompareEpsilon = 0.001; - -@interface FlutterPlatformViewsTestMockPlatformView : UIView -@end -@implementation FlutterPlatformViewsTestMockPlatformView - -- (instancetype)init { - self = [super init]; - if (self) { - gMockPlatformView = self; - } - return self; -} - -- (void)dealloc { - gMockPlatformView = nil; -} - -@end - -@interface FlutterPlatformViewsTestMockFlutterPlatformView : NSObject -@property(nonatomic, strong) UIView* view; -@property(nonatomic, assign) BOOL viewCreated; -@end - -@implementation FlutterPlatformViewsTestMockFlutterPlatformView - -- (instancetype)init { - if (self = [super init]) { - _view = [[FlutterPlatformViewsTestMockPlatformView alloc] init]; - _viewCreated = NO; - } - return self; -} - -- (UIView*)view { - [self checkViewCreatedOnce]; - return _view; -} - -- (void)checkViewCreatedOnce { - if (self.viewCreated) { - abort(); - } - self.viewCreated = YES; -} - -@end - -@interface FlutterPlatformViewsTestMockFlutterPlatformFactory - : NSObject -@end - -@implementation FlutterPlatformViewsTestMockFlutterPlatformFactory -- (NSObject*)createWithFrame:(CGRect)frame - viewIdentifier:(int64_t)viewId - arguments:(id _Nullable)args { - return [[FlutterPlatformViewsTestMockFlutterPlatformView alloc] init]; -} - -@end - -namespace flutter { -namespace { -class FlutterPlatformViewsTestMockPlatformViewDelegate : public PlatformView::Delegate { - public: - void OnPlatformViewCreated(std::unique_ptr surface) override {} - void OnPlatformViewDestroyed() override {} - void OnPlatformViewScheduleFrame() override {} - void OnPlatformViewAddView(int64_t view_id, - const ViewportMetrics& viewport_metrics, - AddViewCallback callback) override {} - void OnPlatformViewRemoveView(int64_t view_id, RemoveViewCallback callback) override {} - void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override {} - void OnPlatformViewSetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) override {} - const flutter::Settings& OnPlatformViewGetSettings() const override { return settings_; } - void OnPlatformViewDispatchPlatformMessage(std::unique_ptr message) override {} - void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr packet) override { - } - void OnPlatformViewDispatchSemanticsAction(int32_t id, - SemanticsAction action, - fml::MallocMapping args) override {} - void OnPlatformViewSetSemanticsEnabled(bool enabled) override {} - void OnPlatformViewSetAccessibilityFeatures(int32_t flags) override {} - void OnPlatformViewRegisterTexture(std::shared_ptr texture) override {} - void OnPlatformViewUnregisterTexture(int64_t texture_id) override {} - void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {} - - void LoadDartDeferredLibrary(intptr_t loading_unit_id, - std::unique_ptr snapshot_data, - std::unique_ptr snapshot_instructions) override { - } - void LoadDartDeferredLibraryError(intptr_t loading_unit_id, - const std::string error_message, - bool transient) override {} - void UpdateAssetResolverByType(std::unique_ptr updated_asset_resolver, - flutter::AssetResolver::AssetResolverType type) override {} - - flutter::Settings settings_; -}; - -BOOL BlurRadiusEqualToBlurRadius(CGFloat radius1, CGFloat radius2) { - const CGFloat epsilon = 0.01; - return std::abs(radius1 - radius2) < epsilon; -} - -} // namespace -} // namespace flutter - -@interface FlutterPlatformViewsTest : XCTestCase -@end - -@implementation FlutterPlatformViewsTest - -namespace { -fml::RefPtr GetDefaultTaskRunner() { - fml::MessageLoop::EnsureInitializedForCurrentThread(); - return fml::MessageLoop::GetCurrent().GetTaskRunner(); -} -} // namespace - -- (void)testFlutterViewOnlyCreateOnceInOneFrame { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // Create embedded view params - flutter::MutatorsStack stack; - // Layer tree always pushes a screen scale factor to the stack - SkMatrix screenScaleMatrix = - SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); - stack.PushTransform(screenScaleMatrix); - // Push a translate matrix - SkMatrix translateMatrix = SkMatrix::Translate(100, 100); - stack.PushTransform(translateMatrix); - SkMatrix finalMatrix; - finalMatrix.setConcat(screenScaleMatrix, translateMatrix); - - auto embeddedViewParams = - std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - - XCTAssertNotNil(gMockPlatformView); - - flutterPlatformViewsController->Reset(); -} - -- (void)testCanCreatePlatformViewWithoutFlutterView { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); -} - -- (void)testChildClippingViewHitTests { - ChildClippingView* childClippingView = - [[ChildClippingView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - UIView* childView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)]; - [childClippingView addSubview:childView]; - - XCTAssertFalse([childClippingView pointInside:CGPointMake(50, 50) withEvent:nil]); - XCTAssertFalse([childClippingView pointInside:CGPointMake(99, 100) withEvent:nil]); - XCTAssertFalse([childClippingView pointInside:CGPointMake(100, 99) withEvent:nil]); - XCTAssertFalse([childClippingView pointInside:CGPointMake(201, 200) withEvent:nil]); - XCTAssertFalse([childClippingView pointInside:CGPointMake(200, 201) withEvent:nil]); - XCTAssertFalse([childClippingView pointInside:CGPointMake(99, 200) withEvent:nil]); - XCTAssertFalse([childClippingView pointInside:CGPointMake(200, 299) withEvent:nil]); - - XCTAssertTrue([childClippingView pointInside:CGPointMake(150, 150) withEvent:nil]); - XCTAssertTrue([childClippingView pointInside:CGPointMake(100, 100) withEvent:nil]); - XCTAssertTrue([childClippingView pointInside:CGPointMake(199, 100) withEvent:nil]); - XCTAssertTrue([childClippingView pointInside:CGPointMake(100, 199) withEvent:nil]); - XCTAssertTrue([childClippingView pointInside:CGPointMake(199, 199) withEvent:nil]); -} - -- (void)testReleasesBackdropFilterSubviewsOnChildClippingViewDealloc { - __weak NSMutableArray* weakBackdropFilterSubviews = nil; - __weak UIVisualEffectView* weakVisualEffectView1 = nil; - __weak UIVisualEffectView* weakVisualEffectView2 = nil; - - @autoreleasepool { - ChildClippingView* clippingView = [[ChildClippingView alloc] initWithFrame:CGRectZero]; - UIVisualEffectView* visualEffectView1 = [[UIVisualEffectView alloc] - initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; - weakVisualEffectView1 = visualEffectView1; - PlatformViewFilter* platformViewFilter1 = - [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) - blurRadius:5 - visualEffectView:visualEffectView1]; - - [clippingView applyBlurBackdropFilters:@[ platformViewFilter1 ]]; - - // Replace the blur filter to validate the original and new UIVisualEffectView are released. - UIVisualEffectView* visualEffectView2 = [[UIVisualEffectView alloc] - initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]]; - weakVisualEffectView2 = visualEffectView2; - PlatformViewFilter* platformViewFilter2 = - [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) - blurRadius:5 - visualEffectView:visualEffectView2]; - [clippingView applyBlurBackdropFilters:@[ platformViewFilter2 ]]; - - weakBackdropFilterSubviews = clippingView.backdropFilterSubviews; - XCTAssertNotNil(weakBackdropFilterSubviews); - clippingView = nil; - } - XCTAssertNil(weakBackdropFilterSubviews); - XCTAssertNil(weakVisualEffectView1); - XCTAssertNil(weakVisualEffectView2); -} - -- (void)testApplyBackdropFilter { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // Create embedded view params - flutter::MutatorsStack stack; - // Layer tree always pushes a screen scale factor to the stack - CGFloat screenScale = [UIScreen mainScreen].scale; - SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); - stack.PushTransform(screenScaleMatrix); - // Push a backdrop filter - auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); - stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - - auto embeddedViewParams = - std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); - ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; - [flutterView addSubview:childClippingView]; - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - // childClippingView has visual effect view with the correct configurations. - NSUInteger numberOfExpectedVisualEffectView = 0; - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u); - if ([self validateOneVisualEffectView:subview - expectedFrame:CGRectMake(0, 0, 10, 10) - inputRadius:5]) { - numberOfExpectedVisualEffectView++; - } - } - XCTAssertEqual(numberOfExpectedVisualEffectView, 1u); -} - -- (void)testApplyBackdropFilterWithCorrectFrame { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // Create embedded view params - flutter::MutatorsStack stack; - // Layer tree always pushes a screen scale factor to the stack - CGFloat screenScale = [UIScreen mainScreen].scale; - SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); - stack.PushTransform(screenScaleMatrix); - // Push a backdrop filter - auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); - stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 8, screenScale * 8)); - - auto embeddedViewParams = - std::make_unique(screenScaleMatrix, SkSize::Make(5, 10), stack); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); - ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; - [flutterView addSubview:childClippingView]; - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - // childClippingView has visual effect view with the correct configurations. - NSUInteger numberOfExpectedVisualEffectView = 0; - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u); - if ([self validateOneVisualEffectView:subview - expectedFrame:CGRectMake(0, 0, 5, 8) - inputRadius:5]) { - numberOfExpectedVisualEffectView++; - } - } - XCTAssertEqual(numberOfExpectedVisualEffectView, 1u); -} - -- (void)testApplyMultipleBackdropFilters { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // Create embedded view params - flutter::MutatorsStack stack; - // Layer tree always pushes a screen scale factor to the stack - CGFloat screenScale = [UIScreen mainScreen].scale; - SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); - stack.PushTransform(screenScaleMatrix); - // Push backdrop filters - for (int i = 0; i < 50; i++) { - auto filter = std::make_shared(i, 2, flutter::DlTileMode::kClamp); - stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - } - - auto embeddedViewParams = - std::make_unique(screenScaleMatrix, SkSize::Make(20, 20), stack); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); - ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; - [flutterView addSubview:childClippingView]; - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - NSUInteger numberOfExpectedVisualEffectView = 0; - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - XCTAssertLessThan(numberOfExpectedVisualEffectView, 50u); - if ([self validateOneVisualEffectView:subview - expectedFrame:CGRectMake(0, 0, 10, 10) - inputRadius:(CGFloat)numberOfExpectedVisualEffectView]) { - numberOfExpectedVisualEffectView++; - } - } - XCTAssertEqual(numberOfExpectedVisualEffectView, (NSUInteger)numberOfExpectedVisualEffectView); -} - -- (void)testAddBackdropFilters { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // Create embedded view params - flutter::MutatorsStack stack; - // Layer tree always pushes a screen scale factor to the stack - CGFloat screenScale = [UIScreen mainScreen].scale; - SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); - stack.PushTransform(screenScaleMatrix); - // Push a backdrop filter - auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); - stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - - auto embeddedViewParams = - std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); - ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; - [flutterView addSubview:childClippingView]; - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - NSMutableArray* originalVisualEffectViews = [[NSMutableArray alloc] init]; - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - XCTAssertLessThan(originalVisualEffectViews.count, 1u); - if ([self validateOneVisualEffectView:subview - expectedFrame:CGRectMake(0, 0, 10, 10) - inputRadius:(CGFloat)5]) { - [originalVisualEffectViews addObject:subview]; - } - } - XCTAssertEqual(originalVisualEffectViews.count, 1u); - - // - // Simulate adding 1 backdrop filter (create a new mutators stack) - // Create embedded view params - flutter::MutatorsStack stack2; - // Layer tree always pushes a screen scale factor to the stack - stack2.PushTransform(screenScaleMatrix); - // Push backdrop filters - for (int i = 0; i < 2; i++) { - stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - } - - embeddedViewParams = std::make_unique(screenScaleMatrix, - SkSize::Make(10, 10), stack2); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - NSMutableArray* newVisualEffectViews = [[NSMutableArray alloc] init]; - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - XCTAssertLessThan(newVisualEffectViews.count, 2u); - - if ([self validateOneVisualEffectView:subview - expectedFrame:CGRectMake(0, 0, 10, 10) - inputRadius:(CGFloat)5]) { - [newVisualEffectViews addObject:subview]; - } - } - XCTAssertEqual(newVisualEffectViews.count, 2u); - for (NSUInteger i = 0; i < originalVisualEffectViews.count; i++) { - UIView* originalView = originalVisualEffectViews[i]; - UIView* newView = newVisualEffectViews[i]; - // Compare reference. - XCTAssertEqual(originalView, newView); - id mockOrignalView = OCMPartialMock(originalView); - OCMReject([mockOrignalView removeFromSuperview]); - [mockOrignalView stopMocking]; - } -} - -- (void)testRemoveBackdropFilters { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // Create embedded view params - flutter::MutatorsStack stack; - // Layer tree always pushes a screen scale factor to the stack - CGFloat screenScale = [UIScreen mainScreen].scale; - SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); - stack.PushTransform(screenScaleMatrix); - // Push backdrop filters - auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); - for (int i = 0; i < 5; i++) { - stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - } - - auto embeddedViewParams = - std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); - ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; - [flutterView addSubview:childClippingView]; - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - NSMutableArray* originalVisualEffectViews = [[NSMutableArray alloc] init]; - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - XCTAssertLessThan(originalVisualEffectViews.count, 5u); - if ([self validateOneVisualEffectView:subview - expectedFrame:CGRectMake(0, 0, 10, 10) - inputRadius:(CGFloat)5]) { - [originalVisualEffectViews addObject:subview]; - } - } - - // Simulate removing 1 backdrop filter (create a new mutators stack) - // Create embedded view params - flutter::MutatorsStack stack2; - // Layer tree always pushes a screen scale factor to the stack - stack2.PushTransform(screenScaleMatrix); - // Push backdrop filters - for (int i = 0; i < 4; i++) { - stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - } - - embeddedViewParams = std::make_unique(screenScaleMatrix, - SkSize::Make(10, 10), stack2); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - NSMutableArray* newVisualEffectViews = [[NSMutableArray alloc] init]; - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - XCTAssertLessThan(newVisualEffectViews.count, 4u); - if ([self validateOneVisualEffectView:subview - expectedFrame:CGRectMake(0, 0, 10, 10) - inputRadius:(CGFloat)5]) { - [newVisualEffectViews addObject:subview]; - } - } - XCTAssertEqual(newVisualEffectViews.count, 4u); - - for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) { - UIView* newView = newVisualEffectViews[i]; - id mockNewView = OCMPartialMock(newView); - UIView* originalView = originalVisualEffectViews[i]; - // Compare reference. - XCTAssertEqual(originalView, newView); - OCMReject([mockNewView removeFromSuperview]); - [mockNewView stopMocking]; - } - - // Simulate removing all backdrop filters (replace the mutators stack) - // Update embedded view params, delete except screenScaleMatrix - for (int i = 0; i < 5; i++) { - stack2.Pop(); - } - // No backdrop filters in the stack, so no nothing to push - - embeddedViewParams = std::make_unique(screenScaleMatrix, - SkSize::Make(10, 10), stack2); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - NSUInteger numberOfExpectedVisualEffectView = 0u; - for (UIView* subview in childClippingView.subviews) { - if ([subview isKindOfClass:[UIVisualEffectView class]]) { - numberOfExpectedVisualEffectView++; - } - } - XCTAssertEqual(numberOfExpectedVisualEffectView, 0u); -} - -- (void)testEditBackdropFilters { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // Create embedded view params - flutter::MutatorsStack stack; - // Layer tree always pushes a screen scale factor to the stack - CGFloat screenScale = [UIScreen mainScreen].scale; - SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); - stack.PushTransform(screenScaleMatrix); - // Push backdrop filters - auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); - for (int i = 0; i < 5; i++) { - stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - } - - auto embeddedViewParams = - std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); - ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; - [flutterView addSubview:childClippingView]; - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - NSMutableArray* originalVisualEffectViews = [[NSMutableArray alloc] init]; - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - XCTAssertLessThan(originalVisualEffectViews.count, 5u); - if ([self validateOneVisualEffectView:subview - expectedFrame:CGRectMake(0, 0, 10, 10) - inputRadius:(CGFloat)5]) { - [originalVisualEffectViews addObject:subview]; - } - } - - // Simulate editing 1 backdrop filter in the middle of the stack (create a new mutators stack) - // Create embedded view params - flutter::MutatorsStack stack2; - // Layer tree always pushes a screen scale factor to the stack - stack2.PushTransform(screenScaleMatrix); - // Push backdrop filters - for (int i = 0; i < 5; i++) { - if (i == 3) { - auto filter2 = - std::make_shared(2, 5, flutter::DlTileMode::kClamp); - - stack2.PushBackdropFilter(filter2, - SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - continue; - } - - stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - } - - embeddedViewParams = std::make_unique(screenScaleMatrix, - SkSize::Make(10, 10), stack2); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - NSMutableArray* newVisualEffectViews = [[NSMutableArray alloc] init]; - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - XCTAssertLessThan(newVisualEffectViews.count, 5u); - CGFloat expectInputRadius = 5; - if (newVisualEffectViews.count == 3) { - expectInputRadius = 2; - } - if ([self validateOneVisualEffectView:subview - expectedFrame:CGRectMake(0, 0, 10, 10) - inputRadius:(CGFloat)expectInputRadius]) { - [newVisualEffectViews addObject:subview]; - } - } - XCTAssertEqual(newVisualEffectViews.count, 5u); - for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) { - UIView* newView = newVisualEffectViews[i]; - id mockNewView = OCMPartialMock(newView); - UIView* originalView = originalVisualEffectViews[i]; - // Compare reference. - XCTAssertEqual(originalView, newView); - OCMReject([mockNewView removeFromSuperview]); - [mockNewView stopMocking]; - } - [newVisualEffectViews removeAllObjects]; - - // Simulate editing 1 backdrop filter in the beginning of the stack (replace the mutators stack) - // Update embedded view params, delete except screenScaleMatrix - for (int i = 0; i < 5; i++) { - stack2.Pop(); - } - // Push backdrop filters - for (int i = 0; i < 5; i++) { - if (i == 0) { - auto filter2 = - std::make_shared(2, 5, flutter::DlTileMode::kClamp); - stack2.PushBackdropFilter(filter2, - SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - continue; - } - - stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - } - - embeddedViewParams = std::make_unique(screenScaleMatrix, - SkSize::Make(10, 10), stack2); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - XCTAssertLessThan(newVisualEffectViews.count, 5u); - CGFloat expectInputRadius = 5; - if (newVisualEffectViews.count == 0) { - expectInputRadius = 2; - } - if ([self validateOneVisualEffectView:subview - expectedFrame:CGRectMake(0, 0, 10, 10) - inputRadius:(CGFloat)expectInputRadius]) { - [newVisualEffectViews addObject:subview]; - } - } - for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) { - UIView* newView = newVisualEffectViews[i]; - id mockNewView = OCMPartialMock(newView); - UIView* originalView = originalVisualEffectViews[i]; - // Compare reference. - XCTAssertEqual(originalView, newView); - OCMReject([mockNewView removeFromSuperview]); - [mockNewView stopMocking]; - } - [newVisualEffectViews removeAllObjects]; - - // Simulate editing 1 backdrop filter in the end of the stack (replace the mutators stack) - // Update embedded view params, delete except screenScaleMatrix - for (int i = 0; i < 5; i++) { - stack2.Pop(); - } - // Push backdrop filters - for (int i = 0; i < 5; i++) { - if (i == 4) { - auto filter2 = - std::make_shared(2, 5, flutter::DlTileMode::kClamp); - stack2.PushBackdropFilter(filter2, - SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - continue; - } - - stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - } - - embeddedViewParams = std::make_unique(screenScaleMatrix, - SkSize::Make(10, 10), stack2); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - XCTAssertLessThan(newVisualEffectViews.count, 5u); - CGFloat expectInputRadius = 5; - if (newVisualEffectViews.count == 4) { - expectInputRadius = 2; - } - if ([self validateOneVisualEffectView:subview - expectedFrame:CGRectMake(0, 0, 10, 10) - inputRadius:(CGFloat)expectInputRadius]) { - [newVisualEffectViews addObject:subview]; - } - } - XCTAssertEqual(newVisualEffectViews.count, 5u); - - for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) { - UIView* newView = newVisualEffectViews[i]; - id mockNewView = OCMPartialMock(newView); - UIView* originalView = originalVisualEffectViews[i]; - // Compare reference. - XCTAssertEqual(originalView, newView); - OCMReject([mockNewView removeFromSuperview]); - [mockNewView stopMocking]; - } - [newVisualEffectViews removeAllObjects]; - - // Simulate editing all backdrop filters in the stack (replace the mutators stack) - // Update embedded view params, delete except screenScaleMatrix - for (int i = 0; i < 5; i++) { - stack2.Pop(); - } - // Push backdrop filters - for (int i = 0; i < 5; i++) { - auto filter2 = std::make_shared(i, 2, flutter::DlTileMode::kClamp); - - stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - } - - embeddedViewParams = std::make_unique(screenScaleMatrix, - SkSize::Make(10, 10), stack2); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - XCTAssertLessThan(newVisualEffectViews.count, 5u); - if ([self validateOneVisualEffectView:subview - expectedFrame:CGRectMake(0, 0, 10, 10) - inputRadius:(CGFloat)newVisualEffectViews.count]) { - [newVisualEffectViews addObject:subview]; - } - } - XCTAssertEqual(newVisualEffectViews.count, 5u); - - for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) { - UIView* newView = newVisualEffectViews[i]; - id mockNewView = OCMPartialMock(newView); - UIView* originalView = originalVisualEffectViews[i]; - // Compare reference. - XCTAssertEqual(originalView, newView); - OCMReject([mockNewView removeFromSuperview]); - [mockNewView stopMocking]; - } - [newVisualEffectViews removeAllObjects]; -} - -- (void)testApplyBackdropFilterNotDlBlurImageFilter { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // Create embedded view params - flutter::MutatorsStack stack; - // Layer tree always pushes a screen scale factor to the stack - CGFloat screenScale = [UIScreen mainScreen].scale; - SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); - stack.PushTransform(screenScaleMatrix); - // Push a dilate backdrop filter - auto dilateFilter = std::make_shared(5, 2); - stack.PushBackdropFilter(dilateFilter, SkRect::MakeEmpty()); - - auto embeddedViewParams = - std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); - ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; - - [flutterView addSubview:childClippingView]; - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - NSUInteger numberOfExpectedVisualEffectView = 0; - for (UIView* subview in childClippingView.subviews) { - if ([subview isKindOfClass:[UIVisualEffectView class]]) { - numberOfExpectedVisualEffectView++; - } - } - XCTAssertEqual(numberOfExpectedVisualEffectView, 0u); - - // Simulate adding a non-DlBlurImageFilter in the middle of the stack (create a new mutators - // stack) Create embedded view params - flutter::MutatorsStack stack2; - // Layer tree always pushes a screen scale factor to the stack - stack2.PushTransform(screenScaleMatrix); - // Push backdrop filters and dilate filter - auto blurFilter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); - - for (int i = 0; i < 5; i++) { - if (i == 2) { - stack2.PushBackdropFilter(dilateFilter, - SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - continue; - } - - stack2.PushBackdropFilter(blurFilter, - SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - } - - embeddedViewParams = std::make_unique(screenScaleMatrix, - SkSize::Make(10, 10), stack2); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - numberOfExpectedVisualEffectView = 0; - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u); - if ([self validateOneVisualEffectView:subview - expectedFrame:CGRectMake(0, 0, 10, 10) - inputRadius:(CGFloat)5]) { - numberOfExpectedVisualEffectView++; - } - } - XCTAssertEqual(numberOfExpectedVisualEffectView, 4u); - - // Simulate adding a non-DlBlurImageFilter to the beginning of the stack (replace the mutators - // stack) Update embedded view params, delete except screenScaleMatrix - for (int i = 0; i < 5; i++) { - stack2.Pop(); - } - // Push backdrop filters and dilate filter - for (int i = 0; i < 5; i++) { - if (i == 0) { - stack2.PushBackdropFilter(dilateFilter, - SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - continue; - } - - stack2.PushBackdropFilter(blurFilter, - SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - } - - embeddedViewParams = std::make_unique(screenScaleMatrix, - SkSize::Make(10, 10), stack2); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - numberOfExpectedVisualEffectView = 0; - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u); - if ([self validateOneVisualEffectView:subview - expectedFrame:CGRectMake(0, 0, 10, 10) - inputRadius:(CGFloat)5]) { - numberOfExpectedVisualEffectView++; - } - } - XCTAssertEqual(numberOfExpectedVisualEffectView, 4u); - - // Simulate adding a non-DlBlurImageFilter to the end of the stack (replace the mutators stack) - // Update embedded view params, delete except screenScaleMatrix - for (int i = 0; i < 5; i++) { - stack2.Pop(); - } - // Push backdrop filters and dilate filter - for (int i = 0; i < 5; i++) { - if (i == 4) { - stack2.PushBackdropFilter(dilateFilter, - SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - continue; - } - - stack2.PushBackdropFilter(blurFilter, - SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - } - - embeddedViewParams = std::make_unique(screenScaleMatrix, - SkSize::Make(10, 10), stack2); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - numberOfExpectedVisualEffectView = 0; - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u); - if ([self validateOneVisualEffectView:subview - expectedFrame:CGRectMake(0, 0, 10, 10) - inputRadius:(CGFloat)5]) { - numberOfExpectedVisualEffectView++; - } - } - XCTAssertEqual(numberOfExpectedVisualEffectView, 4u); - - // Simulate adding only non-DlBlurImageFilter to the stack (replace the mutators stack) - // Update embedded view params, delete except screenScaleMatrix - for (int i = 0; i < 5; i++) { - stack2.Pop(); - } - // Push dilate filters - for (int i = 0; i < 5; i++) { - stack2.PushBackdropFilter(dilateFilter, - SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - } - - embeddedViewParams = std::make_unique(screenScaleMatrix, - SkSize::Make(10, 10), stack2); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - numberOfExpectedVisualEffectView = 0; - for (UIView* subview in childClippingView.subviews) { - if ([subview isKindOfClass:[UIVisualEffectView class]]) { - numberOfExpectedVisualEffectView++; - } - } - XCTAssertEqual(numberOfExpectedVisualEffectView, 0u); -} - -- (void)testApplyBackdropFilterCorrectAPI { - [PlatformViewFilter resetPreparation]; - // The gaussianBlur filter is extracted from UIVisualEffectView. - // Each test requires a new PlatformViewFilter - // Valid UIVisualEffectView API - UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc] - initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; - PlatformViewFilter* platformViewFilter = - [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) - blurRadius:5 - visualEffectView:visualEffectView]; - XCTAssertNotNil(platformViewFilter); -} - -- (void)testApplyBackdropFilterAPIChangedInvalidUIVisualEffectView { - [PlatformViewFilter resetPreparation]; - UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc] init]; - PlatformViewFilter* platformViewFilter = - [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) - blurRadius:5 - visualEffectView:visualEffectView]; - XCTAssertNil(platformViewFilter); -} - -- (void)testApplyBackdropFilterAPIChangedNoGaussianBlurFilter { - [PlatformViewFilter resetPreparation]; - UIVisualEffectView* editedUIVisualEffectView = [[UIVisualEffectView alloc] - initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; - NSArray* subviews = editedUIVisualEffectView.subviews; - for (UIView* view in subviews) { - if ([NSStringFromClass([view class]) hasSuffix:@"BackdropView"]) { - for (CIFilter* filter in view.layer.filters) { - if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) { - [filter setValue:@"notGaussianBlur" forKey:@"name"]; - break; - } - } - break; - } - } - PlatformViewFilter* platformViewFilter = - [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) - blurRadius:5 - visualEffectView:editedUIVisualEffectView]; - XCTAssertNil(platformViewFilter); -} - -- (void)testApplyBackdropFilterAPIChangedInvalidInputRadius { - [PlatformViewFilter resetPreparation]; - UIVisualEffectView* editedUIVisualEffectView = [[UIVisualEffectView alloc] - initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; - NSArray* subviews = editedUIVisualEffectView.subviews; - for (UIView* view in subviews) { - if ([NSStringFromClass([view class]) hasSuffix:@"BackdropView"]) { - for (CIFilter* filter in view.layer.filters) { - if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) { - [filter setValue:@"invalidInputRadius" forKey:@"inputRadius"]; - break; - } - } - break; - } - } - - PlatformViewFilter* platformViewFilter = - [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) - blurRadius:5 - visualEffectView:editedUIVisualEffectView]; - XCTAssertNil(platformViewFilter); -} - -- (void)testBackdropFilterVisualEffectSubviewBackgroundColor { - __weak UIVisualEffectView* weakVisualEffectView; - - @autoreleasepool { - UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc] - initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; - weakVisualEffectView = visualEffectView; - PlatformViewFilter* platformViewFilter = - [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) - blurRadius:5 - visualEffectView:visualEffectView]; - CGColorRef visualEffectSubviewBackgroundColor = nil; - for (UIView* view in [platformViewFilter backdropFilterView].subviews) { - if ([NSStringFromClass([view class]) hasSuffix:@"VisualEffectSubview"]) { - visualEffectSubviewBackgroundColor = view.layer.backgroundColor; - } - } - XCTAssertTrue( - CGColorEqualToColor(visualEffectSubviewBackgroundColor, UIColor.clearColor.CGColor)); - } - XCTAssertNil(weakVisualEffectView); -} - -- (void)testCompositePlatformView { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // Create embedded view params - flutter::MutatorsStack stack; - // Layer tree always pushes a screen scale factor to the stack - SkMatrix screenScaleMatrix = - SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); - stack.PushTransform(screenScaleMatrix); - // Push a translate matrix - SkMatrix translateMatrix = SkMatrix::Translate(100, 100); - stack.PushTransform(translateMatrix); - SkMatrix finalMatrix; - finalMatrix.setConcat(screenScaleMatrix, translateMatrix); - - auto embeddedViewParams = - std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - CGRect platformViewRectInFlutterView = [gMockPlatformView convertRect:gMockPlatformView.bounds - toView:flutterView]; - XCTAssertTrue(CGRectEqualToRect(platformViewRectInFlutterView, CGRectMake(100, 100, 300, 300))); -} - -- (void)testBackdropFilterCorrectlyPushedAndReset { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // Create embedded view params - flutter::MutatorsStack stack; - // Layer tree always pushes a screen scale factor to the stack - CGFloat screenScale = [UIScreen mainScreen].scale; - SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); - stack.PushTransform(screenScaleMatrix); - - auto embeddedViewParams = - std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - - flutterPlatformViewsController->BeginFrame(SkISize::Make(0, 0)); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->PushVisitedPlatformView(2); - auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); - flutterPlatformViewsController->PushFilterToVisitedPlatformViews( - filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); - ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; - [flutterView addSubview:childClippingView]; - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - // childClippingView has visual effect view with the correct configurations. - NSUInteger numberOfExpectedVisualEffectView = 0; - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u); - if ([self validateOneVisualEffectView:subview - expectedFrame:CGRectMake(0, 0, 10, 10) - inputRadius:5]) { - numberOfExpectedVisualEffectView++; - } - } - XCTAssertEqual(numberOfExpectedVisualEffectView, 1u); - - // New frame, with no filter pushed. - auto embeddedViewParams2 = - std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - flutterPlatformViewsController->BeginFrame(SkISize::Make(0, 0)); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams2)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - numberOfExpectedVisualEffectView = 0; - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - numberOfExpectedVisualEffectView++; - } - XCTAssertEqual(numberOfExpectedVisualEffectView, 0u); -} - -- (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // Create embedded view params - flutter::MutatorsStack stack; - // Layer tree always pushes a screen scale factor to the stack - SkMatrix screenScaleMatrix = - SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); - stack.PushTransform(screenScaleMatrix); - // Push a rotate matrix - SkMatrix rotateMatrix; - rotateMatrix.setRotate(10); - stack.PushTransform(rotateMatrix); - SkMatrix finalMatrix; - finalMatrix.setConcat(screenScaleMatrix, rotateMatrix); - - auto embeddedViewParams = - std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - CGRect platformViewRectInFlutterView = [gMockPlatformView convertRect:gMockPlatformView.bounds - toView:flutterView]; - XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); - ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; - // The childclippingview's frame is set based on flow, but the platform view's frame is set based - // on quartz. Although they should be the same, but we should tolerate small floating point - // errors. - XCTAssertLessThan(fabs(platformViewRectInFlutterView.origin.x - childClippingView.frame.origin.x), - kFloatCompareEpsilon); - XCTAssertLessThan(fabs(platformViewRectInFlutterView.origin.y - childClippingView.frame.origin.y), - kFloatCompareEpsilon); - XCTAssertLessThan( - fabs(platformViewRectInFlutterView.size.width - childClippingView.frame.size.width), - kFloatCompareEpsilon); - XCTAssertLessThan( - fabs(platformViewRectInFlutterView.size.height - childClippingView.frame.size.height), - kFloatCompareEpsilon); -} - -- (void)testClipsDoNotInterceptWithPlatformViewShouldNotAddMaskView { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // Create embedded view params. - flutter::MutatorsStack stack; - // Layer tree always pushes a screen scale factor to the stack. - SkMatrix screenScaleMatrix = - SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); - stack.PushTransform(screenScaleMatrix); - SkMatrix translateMatrix = SkMatrix::Translate(5, 5); - // The platform view's rect for this test will be (5, 5, 10, 10). - stack.PushTransform(translateMatrix); - // Push a clip rect, big enough to contain the entire platform view bound. - SkRect rect = SkRect::MakeXYWH(0, 0, 25, 25); - stack.PushClipRect(rect); - // Push a clip rrect, big enough to contain the entire platform view bound without clipping it. - // Make the origin (-1, -1) so that the top left rounded corner isn't clipping the PlatformView. - SkRect rect_for_rrect = SkRect::MakeXYWH(-1, -1, 25, 25); - SkRRect rrect = SkRRect::MakeRectXY(rect_for_rrect, 1, 1); - stack.PushClipRRect(rrect); - - auto embeddedViewParams = std::make_unique( - SkMatrix::Concat(screenScaleMatrix, translateMatrix), SkSize::Make(5, 5), stack); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - gMockPlatformView.backgroundColor = UIColor.redColor; - XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); - ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; - [flutterView addSubview:childClippingView]; - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - XCTAssertNil(childClippingView.maskView); -} - -- (void)testClipRRectOnlyHasCornersInterceptWithPlatformViewShouldAddMaskView { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // Create embedded view params - flutter::MutatorsStack stack; - // Layer tree always pushes a screen scale factor to the stack. - SkMatrix screenScaleMatrix = - SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); - stack.PushTransform(screenScaleMatrix); - SkMatrix translateMatrix = SkMatrix::Translate(5, 5); - // The platform view's rect for this test will be (5, 5, 10, 10). - stack.PushTransform(translateMatrix); - - // Push a clip rrect, the rect of the rrect is the same as the PlatformView of the corner should. - // clip the PlatformView. - SkRect rect_for_rrect = SkRect::MakeXYWH(0, 0, 10, 10); - SkRRect rrect = SkRRect::MakeRectXY(rect_for_rrect, 1, 1); - stack.PushClipRRect(rrect); - - auto embeddedViewParams = std::make_unique( - SkMatrix::Concat(screenScaleMatrix, translateMatrix), SkSize::Make(5, 5), stack); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - gMockPlatformView.backgroundColor = UIColor.redColor; - XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); - ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; - [flutterView addSubview:childClippingView]; - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - XCTAssertNotNil(childClippingView.maskView); -} - -- (void)testClipRect { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // Create embedded view params - flutter::MutatorsStack stack; - // Layer tree always pushes a screen scale factor to the stack - SkMatrix screenScaleMatrix = - SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); - stack.PushTransform(screenScaleMatrix); - // Push a clip rect - SkRect rect = SkRect::MakeXYWH(2, 2, 3, 3); - stack.PushClipRect(rect); - - auto embeddedViewParams = - std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - gMockPlatformView.backgroundColor = UIColor.redColor; - XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); - ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; - [flutterView addSubview:childClippingView]; - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - CGRect insideClipping = CGRectMake(2, 2, 3, 3); - for (int i = 0; i < 10; i++) { - for (int j = 0; j < 10; j++) { - CGPoint point = CGPointMake(i, j); - int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView]; - if (CGRectContainsPoint(insideClipping, point)) { - XCTAssertEqual(alpha, 255); - } else { - XCTAssertEqual(alpha, 0); - } - } - } -} - -- (void)testClipRRect { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // Create embedded view params - flutter::MutatorsStack stack; - // Layer tree always pushes a screen scale factor to the stack - SkMatrix screenScaleMatrix = - SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); - stack.PushTransform(screenScaleMatrix); - // Push a clip rrect - SkRRect rrect = SkRRect::MakeRectXY(SkRect::MakeXYWH(2, 2, 6, 6), 1, 1); - stack.PushClipRRect(rrect); - - auto embeddedViewParams = - std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - gMockPlatformView.backgroundColor = UIColor.redColor; - XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); - ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; - [flutterView addSubview:childClippingView]; - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - /* - ClippingMask outterClipping - 2 3 4 5 6 7 2 3 4 5 6 7 - 2 / - - - - \ 2 + - - - - + - 3 | | 3 | | - 4 | | 4 | | - 5 | | 5 | | - 6 | | 6 | | - 7 \ - - - - / 7 + - - - - + - - innerClipping1 innerClipping2 - 2 3 4 5 6 7 2 3 4 5 6 7 - 2 + - - + 2 - 3 | | 3 + - - - - + - 4 | | 4 | | - 5 | | 5 | | - 6 | | 6 + - - - - + - 7 + - - + 7 - */ - CGRect innerClipping1 = CGRectMake(3, 2, 4, 6); - CGRect innerClipping2 = CGRectMake(2, 3, 6, 4); - CGRect outterClipping = CGRectMake(2, 2, 6, 6); - for (int i = 0; i < 10; i++) { - for (int j = 0; j < 10; j++) { - CGPoint point = CGPointMake(i, j); - int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView]; - if (CGRectContainsPoint(innerClipping1, point) || - CGRectContainsPoint(innerClipping2, point)) { - // Pixels inside either of the 2 inner clippings should be fully opaque. - XCTAssertEqual(alpha, 255); - } else if (CGRectContainsPoint(outterClipping, point)) { - // Corner pixels (i.e. (2, 2), (2, 7), (7, 2) and (7, 7)) should be partially transparent. - XCTAssert(0 < alpha && alpha < 255); - } else { - // Pixels outside outterClipping should be fully transparent. - XCTAssertEqual(alpha, 0); - } - } - } -} - -- (void)testClipPath { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // Create embedded view params - flutter::MutatorsStack stack; - // Layer tree always pushes a screen scale factor to the stack - SkMatrix screenScaleMatrix = - SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); - stack.PushTransform(screenScaleMatrix); - // Push a clip path - SkPath path; - path.addRoundRect(SkRect::MakeXYWH(2, 2, 6, 6), 1, 1); - stack.PushClipPath(path); - - auto embeddedViewParams = - std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - gMockPlatformView.backgroundColor = UIColor.redColor; - XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); - ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; - [flutterView addSubview:childClippingView]; - - [flutterView setNeedsLayout]; - [flutterView layoutIfNeeded]; - - /* - ClippingMask outterClipping - 2 3 4 5 6 7 2 3 4 5 6 7 - 2 / - - - - \ 2 + - - - - + - 3 | | 3 | | - 4 | | 4 | | - 5 | | 5 | | - 6 | | 6 | | - 7 \ - - - - / 7 + - - - - + - - innerClipping1 innerClipping2 - 2 3 4 5 6 7 2 3 4 5 6 7 - 2 + - - + 2 - 3 | | 3 + - - - - + - 4 | | 4 | | - 5 | | 5 | | - 6 | | 6 + - - - - + - 7 + - - + 7 - */ - CGRect innerClipping1 = CGRectMake(3, 2, 4, 6); - CGRect innerClipping2 = CGRectMake(2, 3, 6, 4); - CGRect outterClipping = CGRectMake(2, 2, 6, 6); - for (int i = 0; i < 10; i++) { - for (int j = 0; j < 10; j++) { - CGPoint point = CGPointMake(i, j); - int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView]; - if (CGRectContainsPoint(innerClipping1, point) || - CGRectContainsPoint(innerClipping2, point)) { - // Pixels inside either of the 2 inner clippings should be fully opaque. - XCTAssertEqual(alpha, 255); - } else if (CGRectContainsPoint(outterClipping, point)) { - // Corner pixels (i.e. (2, 2), (2, 7), (7, 2) and (7, 7)) should be partially transparent. - XCTAssert(0 < alpha && alpha < 255); - } else { - // Pixels outside outterClipping should be fully transparent. - XCTAssertEqual(alpha, 0); - } - } - } -} - -- (void)testSetFlutterViewControllerAfterCreateCanStillDispatchTouchEvents { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - // Find touch inteceptor view - UIView* touchInteceptorView = gMockPlatformView; - while (touchInteceptorView != nil && - ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) { - touchInteceptorView = touchInteceptorView.superview; - } - XCTAssertNotNil(touchInteceptorView); - - // Find ForwardGestureRecognizer - UIGestureRecognizer* forwardGectureRecognizer = nil; - for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) { - if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) { - forwardGectureRecognizer = gestureRecognizer; - break; - } - } - - // Before setting flutter view controller, events are not dispatched. - NSSet* touches1 = [[NSSet alloc] init]; - id event1 = OCMClassMock([UIEvent class]); - id flutterViewContoller = OCMClassMock([FlutterViewController class]); - [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1]; - OCMReject([flutterViewContoller touchesBegan:touches1 withEvent:event1]); - - // Set flutter view controller allows events to be dispatched. - NSSet* touches2 = [[NSSet alloc] init]; - id event2 = OCMClassMock([UIEvent class]); - flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller); - [forwardGectureRecognizer touchesBegan:touches2 withEvent:event2]; - OCMVerify([flutterViewContoller touchesBegan:touches2 withEvent:event2]); -} - -- (void)testSetFlutterViewControllerInTheMiddleOfTouchEventShouldStillAllowGesturesToBeHandled { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - // Find touch inteceptor view - UIView* touchInteceptorView = gMockPlatformView; - while (touchInteceptorView != nil && - ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) { - touchInteceptorView = touchInteceptorView.superview; - } - XCTAssertNotNil(touchInteceptorView); - - // Find ForwardGestureRecognizer - UIGestureRecognizer* forwardGectureRecognizer = nil; - for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) { - if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) { - forwardGectureRecognizer = gestureRecognizer; - break; - } - } - id flutterViewContoller = OCMClassMock([FlutterViewController class]); - { - // ***** Sequence 1, finishing touch event with touchEnded ***** // - flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller); - - NSSet* touches1 = [[NSSet alloc] init]; - id event1 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1]; - OCMVerify([flutterViewContoller touchesBegan:touches1 withEvent:event1]); - - flutterPlatformViewsController->SetFlutterViewController(nil); - - // Allow the touch events to finish - NSSet* touches2 = [[NSSet alloc] init]; - id event2 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesMoved:touches2 withEvent:event2]; - OCMVerify([flutterViewContoller touchesMoved:touches2 withEvent:event2]); - - NSSet* touches3 = [[NSSet alloc] init]; - id event3 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesEnded:touches3 withEvent:event3]; - OCMVerify([flutterViewContoller touchesEnded:touches3 withEvent:event3]); - - // Now the 2nd touch sequence should not be allowed. - NSSet* touches4 = [[NSSet alloc] init]; - id event4 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesBegan:touches4 withEvent:event4]; - OCMReject([flutterViewContoller touchesBegan:touches4 withEvent:event4]); - - NSSet* touches5 = [[NSSet alloc] init]; - id event5 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5]; - OCMReject([flutterViewContoller touchesEnded:touches5 withEvent:event5]); - } - - { - // ***** Sequence 2, finishing touch event with touchCancelled ***** // - flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller); - - NSSet* touches1 = [[NSSet alloc] init]; - id event1 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1]; - OCMVerify([flutterViewContoller touchesBegan:touches1 withEvent:event1]); - - flutterPlatformViewsController->SetFlutterViewController(nil); - - // Allow the touch events to finish - NSSet* touches2 = [[NSSet alloc] init]; - id event2 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesMoved:touches2 withEvent:event2]; - OCMVerify([flutterViewContoller touchesMoved:touches2 withEvent:event2]); - - NSSet* touches3 = [[NSSet alloc] init]; - id event3 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesCancelled:touches3 withEvent:event3]; - OCMVerify([flutterViewContoller forceTouchesCancelled:touches3]); - - // Now the 2nd touch sequence should not be allowed. - NSSet* touches4 = [[NSSet alloc] init]; - id event4 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesBegan:touches4 withEvent:event4]; - OCMReject([flutterViewContoller touchesBegan:touches4 withEvent:event4]); - - NSSet* touches5 = [[NSSet alloc] init]; - id event5 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5]; - OCMReject([flutterViewContoller touchesEnded:touches5 withEvent:event5]); - } - - flutterPlatformViewsController->Reset(); -} - -- (void) - testSetFlutterViewControllerInTheMiddleOfTouchEventAllowsTheNewControllerToHandleSecondTouchSequence { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - // Find touch inteceptor view - UIView* touchInteceptorView = gMockPlatformView; - while (touchInteceptorView != nil && - ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) { - touchInteceptorView = touchInteceptorView.superview; - } - XCTAssertNotNil(touchInteceptorView); - - // Find ForwardGestureRecognizer - UIGestureRecognizer* forwardGectureRecognizer = nil; - for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) { - if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) { - forwardGectureRecognizer = gestureRecognizer; - break; - } - } - id flutterViewContoller = OCMClassMock([FlutterViewController class]); - - flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller); - - // The touches in this sequence requires 1 touch object, we always create the NSSet with one item. - NSSet* touches1 = [NSSet setWithObject:@1]; - id event1 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1]; - OCMVerify([flutterViewContoller touchesBegan:touches1 withEvent:event1]); - - FlutterViewController* flutterViewContoller2 = OCMClassMock([FlutterViewController class]); - flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller2); - - // Touch events should still send to the old FlutterViewController if FlutterViewController - // is updated in between. - NSSet* touches2 = [NSSet setWithObject:@1]; - id event2 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesBegan:touches2 withEvent:event2]; - OCMVerify([flutterViewContoller touchesBegan:touches2 withEvent:event2]); - OCMReject([flutterViewContoller2 touchesBegan:touches2 withEvent:event2]); - - NSSet* touches3 = [NSSet setWithObject:@1]; - id event3 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesMoved:touches3 withEvent:event3]; - OCMVerify([flutterViewContoller touchesMoved:touches3 withEvent:event3]); - OCMReject([flutterViewContoller2 touchesMoved:touches3 withEvent:event3]); - - NSSet* touches4 = [NSSet setWithObject:@1]; - id event4 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesEnded:touches4 withEvent:event4]; - OCMVerify([flutterViewContoller touchesEnded:touches4 withEvent:event4]); - OCMReject([flutterViewContoller2 touchesEnded:touches4 withEvent:event4]); - - NSSet* touches5 = [NSSet setWithObject:@1]; - id event5 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5]; - OCMVerify([flutterViewContoller touchesEnded:touches5 withEvent:event5]); - OCMReject([flutterViewContoller2 touchesEnded:touches5 withEvent:event5]); - - // Now the 2nd touch sequence should go to the new FlutterViewController - - NSSet* touches6 = [NSSet setWithObject:@1]; - id event6 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesBegan:touches6 withEvent:event6]; - OCMVerify([flutterViewContoller2 touchesBegan:touches6 withEvent:event6]); - OCMReject([flutterViewContoller touchesBegan:touches6 withEvent:event6]); - - // Allow the touch events to finish - NSSet* touches7 = [NSSet setWithObject:@1]; - id event7 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesMoved:touches7 withEvent:event7]; - OCMVerify([flutterViewContoller2 touchesMoved:touches7 withEvent:event7]); - OCMReject([flutterViewContoller touchesMoved:touches7 withEvent:event7]); - - NSSet* touches8 = [NSSet setWithObject:@1]; - id event8 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesEnded:touches8 withEvent:event8]; - OCMVerify([flutterViewContoller2 touchesEnded:touches8 withEvent:event8]); - OCMReject([flutterViewContoller touchesEnded:touches8 withEvent:event8]); - - flutterPlatformViewsController->Reset(); -} - -- (void)testFlutterPlatformViewTouchesCancelledEventAreForcedToBeCancelled { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - // Find touch inteceptor view - UIView* touchInteceptorView = gMockPlatformView; - while (touchInteceptorView != nil && - ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) { - touchInteceptorView = touchInteceptorView.superview; - } - XCTAssertNotNil(touchInteceptorView); - - // Find ForwardGestureRecognizer - UIGestureRecognizer* forwardGectureRecognizer = nil; - for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) { - if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) { - forwardGectureRecognizer = gestureRecognizer; - break; - } - } - id flutterViewContoller = OCMClassMock([FlutterViewController class]); - - flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller); - - NSSet* touches1 = [NSSet setWithObject:@1]; - id event1 = OCMClassMock([UIEvent class]); - [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1]; - - [forwardGectureRecognizer touchesCancelled:touches1 withEvent:event1]; - OCMVerify([flutterViewContoller forceTouchesCancelled:touches1]); - - flutterPlatformViewsController->Reset(); -} - -- (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashing { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - - // Create embedded view params - flutter::MutatorsStack stack; - SkMatrix finalMatrix; - - auto embeddedViewParams_1 = - std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams_1)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - flutter::SurfaceFrame::FramebufferInfo framebuffer_info; - auto mock_surface = std::make_unique( - nullptr, framebuffer_info, - [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return false; }, - [](const flutter::SurfaceFrame& surface_frame) { return true; }, - /*frame_size=*/SkISize::Make(800, 600)); - XCTAssertFalse( - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); - - auto embeddedViewParams_2 = - std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams_2)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - auto mock_surface_submit_true = std::make_unique( - nullptr, framebuffer_info, - [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, - [](const flutter::SurfaceFrame& surface_frame) { return true; }, - /*frame_size=*/SkISize::Make(800, 600)); - XCTAssertTrue(flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, - std::move(mock_surface_submit_true))); -} - -- (void) - testFlutterPlatformViewControllerResetDeallocsPlatformViewWhenRootViewsNotBindedToFlutterView { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - // autorelease pool to trigger an autorelease for all the root_views_ and touch_interceptors_. - @autoreleasepool { - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - flutter::MutatorsStack stack; - SkMatrix finalMatrix; - auto embeddedViewParams = - std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - - // Not calling |flutterPlatformViewsController::SubmitFrame| so that the platform views are not - // added to flutter_view_. - - XCTAssertNotNil(gMockPlatformView); - flutterPlatformViewsController->Reset(); - } - XCTAssertNil(gMockPlatformView); -} - -- (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}], - result); - - // First frame, |EmbeddedViewCount| 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->CompositeWithParams( - 0, flutterPlatformViewsController->GetCompositionParams(0)); - - XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 1UL); - - // Second frame, |EmbeddedViewCount| should be empty at the start - flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); - XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 0UL); - - auto embeddedViewParams2 = - std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams2)); - flutterPlatformViewsController->CompositeWithParams( - 0, flutterPlatformViewsController->GetCompositionParams(0)); - - XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 1UL); -} - -- (void) - testFlutterPlatformViewControllerSubmitFrameShouldOrderSubviewsCorrectlyWithDifferentViewHierarchy { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}], - result); - UIView* view1 = gMockPlatformView; - - // This overwrites `gMockPlatformView` to another view. - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}], - result); - UIView* view2 = gMockPlatformView; - - 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)); - - auto embeddedViewParams2 = - std::make_unique(finalMatrix, SkSize::Make(500, 500), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams2)); - - // SKSurface is required if the root FlutterView is present. - const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000); - sk_sp mock_sk_surface = SkSurfaces::Raster(image_info); - flutter::SurfaceFrame::FramebufferInfo framebuffer_info; - auto mock_surface = std::make_unique( - std::move(mock_sk_surface), framebuffer_info, - [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, - [](const flutter::SurfaceFrame& surface_frame) { return true; }, - /*frame_size=*/SkISize::Make(800, 600)); - - XCTAssertTrue( - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); - - // platform view is wrapped by touch interceptor, which itself is wrapped by clipping view. - UIView* clippingView1 = view1.superview.superview; - UIView* clippingView2 = view2.superview.superview; - XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] < - [flutterView.subviews indexOfObject:clippingView2], - @"The first clipping view should be added before the second clipping view."); - - // Need to recreate these params since they are `std::move`ed. - flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); - // Process the second frame in the opposite order. - embeddedViewParams2 = - std::make_unique(finalMatrix, SkSize::Make(500, 500), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams2)); - - embeddedViewParams1 = - std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams1)); - - mock_sk_surface = SkSurfaces::Raster(image_info); - mock_surface = std::make_unique( - std::move(mock_sk_surface), framebuffer_info, - [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, - [](const flutter::SurfaceFrame& surface_frame) { return true; }, - /*frame_size=*/SkISize::Make(800, 600)); - XCTAssertTrue( - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); - - XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] > - [flutterView.subviews indexOfObject:clippingView2], - @"The first clipping view should be added after the second clipping view."); -} - -- (void) - testFlutterPlatformViewControllerSubmitFrameShouldOrderSubviewsCorrectlyWithSameViewHierarchy { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}], - result); - UIView* view1 = gMockPlatformView; - - // This overwrites `gMockPlatformView` to another view. - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}], - result); - UIView* view2 = gMockPlatformView; - - 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)); - - auto embeddedViewParams2 = - std::make_unique(finalMatrix, SkSize::Make(500, 500), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams2)); - - // SKSurface is required if the root FlutterView is present. - const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000); - sk_sp mock_sk_surface = SkSurfaces::Raster(image_info); - flutter::SurfaceFrame::FramebufferInfo framebuffer_info; - auto mock_surface = std::make_unique( - std::move(mock_sk_surface), framebuffer_info, - [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, - [](const flutter::SurfaceFrame& surface_frame) { return true; }, - /*frame_size=*/SkISize::Make(800, 600)); - - XCTAssertTrue( - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); - - // platform view is wrapped by touch interceptor, which itself is wrapped by clipping view. - UIView* clippingView1 = view1.superview.superview; - UIView* clippingView2 = view2.superview.superview; - XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] < - [flutterView.subviews indexOfObject:clippingView2], - @"The first clipping view should be added before the second clipping view."); - - // Need to recreate these params since they are `std::move`ed. - flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); - // Process the second frame in the same order. - embeddedViewParams1 = - std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams1)); - - embeddedViewParams2 = - std::make_unique(finalMatrix, SkSize::Make(500, 500), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams2)); - - mock_sk_surface = SkSurfaces::Raster(image_info); - mock_surface = std::make_unique( - std::move(mock_sk_surface), framebuffer_info, - [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, - [](const flutter::SurfaceFrame& surface_frame) { return true; }, - /*frame_size=*/SkISize::Make(800, 600)); - XCTAssertTrue( - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); - - XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] < - [flutterView.subviews indexOfObject:clippingView2], - @"The first clipping view should be added before the second clipping view."); -} - -- (int)alphaOfPoint:(CGPoint)point onView:(UIView*)view { - unsigned char pixel[4] = {0}; - - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - - // Draw the pixel on `point` in the context. - CGContextRef context = CGBitmapContextCreate( - pixel, 1, 1, 8, 4, colorSpace, kCGBitmapAlphaInfoMask & kCGImageAlphaPremultipliedLast); - CGContextTranslateCTM(context, -point.x, -point.y); - [view.layer renderInContext:context]; - - CGContextRelease(context); - CGColorSpaceRelease(colorSpace); - // Get the alpha from the pixel that we just rendered. - return pixel[3]; -} - -- (void)testHasFirstResponderInViewHierarchySubtree_viewItselfBecomesFirstResponder { - // For view to become the first responder, it must be a descendant of a UIWindow - UIWindow* window = [[UIWindow alloc] init]; - UITextField* textField = [[UITextField alloc] init]; - [window addSubview:textField]; - - [textField becomeFirstResponder]; - XCTAssertTrue(textField.isFirstResponder); - XCTAssertTrue(textField.flt_hasFirstResponderInViewHierarchySubtree); - [textField resignFirstResponder]; - XCTAssertFalse(textField.isFirstResponder); - XCTAssertFalse(textField.flt_hasFirstResponderInViewHierarchySubtree); -} - -- (void)testHasFirstResponderInViewHierarchySubtree_descendantViewBecomesFirstResponder { - // For view to become the first responder, it must be a descendant of a UIWindow - UIWindow* window = [[UIWindow alloc] init]; - UIView* view = [[UIView alloc] init]; - UIView* childView = [[UIView alloc] init]; - UITextField* textField = [[UITextField alloc] init]; - [window addSubview:view]; - [view addSubview:childView]; - [childView addSubview:textField]; - - [textField becomeFirstResponder]; - XCTAssertTrue(textField.isFirstResponder); - XCTAssertTrue(view.flt_hasFirstResponderInViewHierarchySubtree); - [textField resignFirstResponder]; - XCTAssertFalse(textField.isFirstResponder); - XCTAssertFalse(view.flt_hasFirstResponderInViewHierarchySubtree); -} - -- (void)testFlutterClippingMaskViewPoolReuseViewsAfterRecycle { - FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2]; - FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero]; - FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero]; - [pool insertViewToPoolIfNeeded:view1]; - [pool insertViewToPoolIfNeeded:view2]; - CGRect newRect = CGRectMake(0, 0, 10, 10); - FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:newRect]; - FlutterClippingMaskView* view4 = [pool getMaskViewWithFrame:newRect]; - // view3 and view4 should randomly get either of view1 and view2. - NSSet* set1 = [NSSet setWithObjects:view1, view2, nil]; - NSSet* set2 = [NSSet setWithObjects:view3, view4, nil]; - XCTAssertEqualObjects(set1, set2); - XCTAssertTrue(CGRectEqualToRect(view3.frame, newRect)); - XCTAssertTrue(CGRectEqualToRect(view4.frame, newRect)); -} - -- (void)testFlutterClippingMaskViewPoolAllocsNewMaskViewsAfterReachingCapacity { - FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2]; - FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero]; - FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero]; - FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:CGRectZero]; - XCTAssertNotEqual(view1, view3); - XCTAssertNotEqual(view2, view3); -} - -- (void)testMaskViewsReleasedWhenPoolIsReleased { - __weak UIView* weakView; - @autoreleasepool { - FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2]; - FlutterClippingMaskView* view = [pool getMaskViewWithFrame:CGRectZero]; - weakView = view; - XCTAssertNotNil(weakView); - } - XCTAssertNil(weakView); -} - -- (void)testClipMaskViewIsReused { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // 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( - screenScaleMatrix, SkSize::Make(10, 10), stack1); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1)); - flutterPlatformViewsController->CompositeWithParams( - 1, flutterPlatformViewsController->GetCompositionParams(1)); - - UIView* childClippingView1 = gMockPlatformView.superview.superview; - UIView* maskView1 = childClippingView1.maskView; - XCTAssertNotNil(maskView1); - - // Composite a new frame. - flutterPlatformViewsController->BeginFrame(SkISize::Make(100, 100)); - flutter::MutatorsStack stack2; - auto embeddedViewParams2 = std::make_unique( - screenScaleMatrix, SkSize::Make(10, 10), stack2); - auto embeddedViewParams3 = std::make_unique( - screenScaleMatrix, SkSize::Make(10, 10), stack2); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams3)); - flutterPlatformViewsController->CompositeWithParams( - 1, flutterPlatformViewsController->GetCompositionParams(1)); - - childClippingView1 = gMockPlatformView.superview.superview; - - // This overrides gMockPlatformView to point to the newly created platform view. - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - - auto embeddedViewParams4 = std::make_unique( - screenScaleMatrix, SkSize::Make(10, 10), stack1); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams4)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - UIView* childClippingView2 = gMockPlatformView.superview.superview; - - UIView* maskView2 = childClippingView2.maskView; - XCTAssertEqual(maskView1, maskView2); - XCTAssertNotNil(childClippingView2.maskView); - XCTAssertNil(childClippingView1.maskView); -} - -- (void)testDifferentClipMaskViewIsUsedForEachView { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - 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* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // 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( - screenScaleMatrix, SkSize::Make(10, 10), stack1); - - flutter::MutatorsStack stack2; - stack2.PushClipRect(rect); - auto embeddedViewParams2 = std::make_unique( - screenScaleMatrix, SkSize::Make(10, 10), stack2); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1)); - flutterPlatformViewsController->CompositeWithParams( - 1, flutterPlatformViewsController->GetCompositionParams(1)); - - UIView* childClippingView1 = view1.superview.superview; - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams2)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); - - UIView* childClippingView2 = view2.superview.superview; - UIView* maskView1 = childClippingView1.maskView; - UIView* maskView2 = childClippingView2.maskView; - XCTAssertNotEqual(maskView1, maskView2); -} - -- (void)testMaskViewUsesCAShapeLayerAsTheBackingLayer { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}], - result); - - XCTAssertNotNil(gMockPlatformView); - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // 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( - screenScaleMatrix, SkSize::Make(10, 10), stack1); - - flutter::MutatorsStack stack2; - stack2.PushClipRect(rect); - auto embeddedViewParams2 = std::make_unique( - screenScaleMatrix, SkSize::Make(10, 10), stack2); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1)); - flutterPlatformViewsController->CompositeWithParams( - 1, flutterPlatformViewsController->GetCompositionParams(1)); - - UIView* childClippingView = gMockPlatformView.superview.superview; - - UIView* maskView = childClippingView.maskView; - XCTAssert([maskView.layer isKindOfClass:[CAShapeLayer class]], - @"Mask view must use CAShapeLayer as its backing layer."); -} - -// Return true if a correct visual effect view is found. It also implies all the validation in this -// method passes. -// -// There are two fail states for this method. 1. One of the XCTAssert method failed; or 2. No -// correct visual effect view found. -- (BOOL)validateOneVisualEffectView:(UIView*)visualEffectView - expectedFrame:(CGRect)frame - inputRadius:(CGFloat)inputRadius { - XCTAssertTrue(CGRectEqualToRect(visualEffectView.frame, frame)); - for (UIView* view in visualEffectView.subviews) { - if (![NSStringFromClass([view class]) hasSuffix:@"BackdropView"]) { - continue; - } - XCTAssertEqual(view.layer.filters.count, 1u); - NSObject* filter = view.layer.filters.firstObject; - - XCTAssertEqualObjects([filter valueForKey:@"name"], @"gaussianBlur"); - - NSObject* inputRadiusInFilter = [filter valueForKey:@"inputRadius"]; - XCTAssertTrue([inputRadiusInFilter isKindOfClass:[NSNumber class]] && - flutter::BlurRadiusEqualToBlurRadius(((NSNumber*)inputRadiusInFilter).floatValue, - inputRadius)); - return YES; - } - return NO; -} - -- (void)testDisposingViewInCompositionOrderDoNotCrash { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}], - result); - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}], - result); - - { - // **** First frame, view id 0, 1 in the composition_order_, disposing view 0 is called. **** // - // No view should be disposed, or removed from the composition order. - flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); - flutter::MutatorsStack stack; - SkMatrix finalMatrix; - auto embeddedViewParams0 = - std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams0)); - - auto embeddedViewParams1 = - std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1)); - - XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 2UL); - - XCTestExpectation* expectation = [self expectationWithDescription:@"dispose call ended."]; - FlutterResult disposeResult = ^(id result) { - [expectation fulfill]; - }; - - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall methodCallWithMethodName:@"dispose" arguments:@0], disposeResult); - [self waitForExpectationsWithTimeout:30 handler:nil]; - - const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000); - sk_sp mock_sk_surface = SkSurfaces::Raster(image_info); - flutter::SurfaceFrame::FramebufferInfo framebuffer_info; - auto mock_surface = std::make_unique( - std::move(mock_sk_surface), framebuffer_info, - [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, - [](const flutter::SurfaceFrame& surface_frame) { return true; }, - /*frame_size=*/SkISize::Make(800, 600)); - XCTAssertTrue( - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); - - // Disposing won't remove embedded views until the view is removed from the composition_order_ - XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 2UL); - XCTAssertNotNil(flutterPlatformViewsController->GetPlatformViewByID(0)); - XCTAssertNotNil(flutterPlatformViewsController->GetPlatformViewByID(1)); - } - - { - // **** Second frame, view id 1 in the composition_order_, no disposing view is called, **** // - // View 0 is removed from the composition order in this frame, hence also disposed. - flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); - flutter::MutatorsStack stack; - SkMatrix finalMatrix; - auto embeddedViewParams1 = - std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1)); - - const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000); - sk_sp mock_sk_surface = SkSurfaces::Raster(image_info); - flutter::SurfaceFrame::FramebufferInfo framebuffer_info; - auto mock_surface = std::make_unique( - std::move(mock_sk_surface), framebuffer_info, - [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, - [](const flutter::SurfaceFrame& surface_frame) { return true; }, - /*frame_size=*/SkISize::Make(800, 600)); - XCTAssertTrue( - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); - - // Disposing won't remove embedded views until the view is removed from the composition_order_ - XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 1UL); - XCTAssertNil(flutterPlatformViewsController->GetPlatformViewByID(0)); - XCTAssertNotNil(flutterPlatformViewsController->GetPlatformViewByID(1)); - } -} -- (void)testOnlyPlatformViewsAreRemovedWhenReset { - flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; - - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/GetDefaultTaskRunner(), - /*raster=*/GetDefaultTaskRunner(), - /*ui=*/GetDefaultTaskRunner(), - /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/flutterPlatformViewsController, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_jsync_switch=*/std::make_shared()); - - FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = - [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); - FlutterResult result = ^(id result) { - }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); - UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); - // Create embedded view params - flutter::MutatorsStack stack; - // Layer tree always pushes a screen scale factor to the stack - SkMatrix screenScaleMatrix = - SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); - stack.PushTransform(screenScaleMatrix); - // Push a translate matrix - SkMatrix translateMatrix = SkMatrix::Translate(100, 100); - stack.PushTransform(translateMatrix); - SkMatrix finalMatrix; - finalMatrix.setConcat(screenScaleMatrix, translateMatrix); - - auto embeddedViewParams = - std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - - // SKSurface is required if the root FlutterView is present. - const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000); - sk_sp mock_sk_surface = SkSurfaces::Raster(image_info); - flutter::SurfaceFrame::FramebufferInfo framebuffer_info; - auto mock_surface = std::make_unique( - std::move(mock_sk_surface), framebuffer_info, - [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, - [](const flutter::SurfaceFrame& surface_frame) { return true; }, - /*frame_size=*/SkISize::Make(800, 600)); - - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface)); - - UIView* someView = [[UIView alloc] init]; - [flutterView addSubview:someView]; - - flutterPlatformViewsController->Reset(); - XCTAssertEqual(flutterView.subviews.count, 1u); - XCTAssertEqual(flutterView.subviews.firstObject, someView); -} - -- (void)testFlutterTouchInterceptingViewLinksToAccessibilityContainer { - FlutterTouchInterceptingView* touchInteceptorView = [[FlutterTouchInterceptingView alloc] init]; - NSObject* container = [[NSObject alloc] init]; - [touchInteceptorView setFlutterAccessibilityContainer:container]; - XCTAssertEqualObjects([touchInteceptorView accessibilityContainer], container); -} - -- (void)testLayerPool { - // Create an IOSContext and GrDirectContext. - FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"]; - [engine run]; - XCTAssertTrue([engine iosPlatformView] != nullptr); - auto ios_context = [engine iosPlatformView]->GetIosContext(); - auto gr_context = ios_context->GetMainContext(); - - auto pool = flutter::OverlayLayerPool{}; - - // Add layers to the pool. - pool.CreateLayer(gr_context.get(), ios_context, MTLPixelFormatBGRA8Unorm); - XCTAssertEqual(pool.size(), 1u); - pool.CreateLayer(gr_context.get(), ios_context, MTLPixelFormatBGRA8Unorm); - XCTAssertEqual(pool.size(), 2u); - - // Mark all layers as unused. - pool.RecycleLayers(); - XCTAssertEqual(pool.size(), 2u); - - // Free the unused layers. One should remain. - auto unused_layers = pool.RemoveUnusedLayers(); - XCTAssertEqual(unused_layers.size(), 2u); - XCTAssertEqual(pool.size(), 1u); -} - -@end From 98f28a46a6d3e90ad0add219f9c97377735dd77c Mon Sep 17 00:00:00 2001 From: jonahwilliams Date: Tue, 6 Aug 2024 11:49:25 -0700 Subject: [PATCH 5/6] ++ --- .../darwin/ios/framework/Source/FlutterPlatformViewsTest.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index 7bfc93904ac02..05e999777b097 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -124,7 +124,7 @@ void UpdateAssetResolverByType(std::unique_ptr updated_a BOOL BlurRadiusEqualToBlurRadius(CGFloat radius1, CGFloat radius2) { const CGFloat epsilon = 0.01; - return radius1 - radius2 < epsilon; + return std::abs(radius1 - radius2) < epsilon; } } // namespace From 40e9ca5952fb8c731a8ad1802897b531a1d4094b Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 6 Aug 2024 13:34:45 -0700 Subject: [PATCH 6/6] Update shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm Co-authored-by: Chris Bracken --- .../ios/framework/Source/FlutterPlatformViews_Internal.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm index f1fd2ee2818df..2a90c27a6a415 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm @@ -462,7 +462,7 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { } @end -// This recognizers delays touch events from being dispatched to the responder chain until it failed +// This recognizer delays touch events from being dispatched to the responder chain until it failed // recognizing a gesture. // // We only fail this recognizer when asked to do so by the Flutter framework (which does so by