when resetting FlutterPlatformViewsController, clear out some additional internal state to prevent it from carrying over across a Hot Restart (#164456)

<!--
Thanks for filing a pull request!
Reviewers are typically assigned within a week of filing a request.
To learn more about code review, see our documentation on Tree Hygiene:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
-->

When performing a Hot Restart on iOS in an app that uses PlatformViews,
a "recreating_view" PlatformException can be thrown. This happens
because some of the state of FlutterPlatformViewsController is not
cleared as part of the Hot Restart. Specifically,
`self.previousCompositionOrder` will have its previous value from before
the Hot Restart. The fix here clears that state as part of the Hot
Restart.

Fixes https://github.com/flutter/flutter/issues/163935

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
Paul Sturm
2025-03-04 18:53:43 -06:00
committed by GitHub
parent f3d0f5a2ca
commit 99d265e484
3 changed files with 79 additions and 0 deletions

View File

@@ -139,6 +139,8 @@ NS_ASSUME_NONNULL_BEGIN
- (const flutter::EmbeddedViewParams&)compositionParamsForView:(int64_t)viewId;
- (std::vector<int64_t>&)previousCompositionOrder;
@end
NS_ASSUME_NONNULL_END

View File

@@ -675,6 +675,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
[self.platformViews[viewId].root_view removeFromSuperview];
}
self.platformViews.clear();
self.previousCompositionOrder.clear();
});
self.compositionOrder.clear();

View File

@@ -4458,6 +4458,82 @@ fml::RefPtr<fml::TaskRunner> GetDefaultTaskRunner() {
XCTAssertEqual(flutterView.subviews.firstObject, someView);
}
- (void)testResetClearsPreviousCompositionOrder {
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
/*platform=*/GetDefaultTaskRunner(),
/*raster=*/GetDefaultTaskRunner(),
/*ui=*/GetDefaultTaskRunner(),
/*io=*/GetDefaultTaskRunner());
FlutterPlatformViewsController* flutterPlatformViewsController =
[[FlutterPlatformViewsController alloc] init];
flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
/*delegate=*/mock_delegate,
/*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
/*platform_views_controller=*/flutterPlatformViewsController,
/*task_runners=*/runners,
/*worker_task_runner=*/nil,
/*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
[[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init];
[flutterPlatformViewsController
registerViewFactory:factory
withId:@"MockFlutterPlatformView"
gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
FlutterResult result = ^(id result) {
};
[flutterPlatformViewsController
onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create"
arguments:@{
@"id" : @2,
@"viewType" : @"MockFlutterPlatformView"
}]
result:result];
FlutterPlatformViewsTestMockFlutterViewController* mockFlutterViewController =
[[FlutterPlatformViewsTestMockFlutterViewController alloc] init];
flutterPlatformViewsController.flutterViewController = mockFlutterViewController;
UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
flutterPlatformViewsController.flutterView = flutterView;
// Create embedded view params
flutter::MutatorsStack stack;
// Layer tree always pushes a screen scale factor to the stack
CGFloat screenScale = [mockFlutterViewController flutterScreenIfViewLoaded].scale;
SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale);
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<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
[flutterPlatformViewsController prerollCompositeEmbeddedView:2
withParams:std::move(embeddedViewParams)];
flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
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), nullptr, /*display_list_fallback=*/true);
[flutterPlatformViewsController submitFrame:std::move(mock_surface)
withIosContext:std::make_shared<flutter::IOSContextNoop>()];
// The above code should result in previousCompositionOrder having one viewId in it
XCTAssertEqual(flutterPlatformViewsController.previousCompositionOrder.size(), 1ul);
// reset should clear previousCompositionOrder
[flutterPlatformViewsController reset];
// previousCompositionOrder should now be empty
XCTAssertEqual(flutterPlatformViewsController.previousCompositionOrder.size(), 0ul);
}
- (void)testNilPlatformViewDoesntCrash {
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;