Revert "[iOS][Keyboard] Wait vsync on UI thread and update viewport inset to avoid jitter." (flutter/engine#43422)
Reverts flutter/engine#42312 Original PR caused crash https://github.com/flutter/flutter/issues/130028 Will reopen https://github.com/flutter/flutter/issues/120555
This commit is contained in:
@@ -333,12 +333,7 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
|
||||
return _shell->GetTaskRunners().GetPlatformTaskRunner();
|
||||
}
|
||||
|
||||
- (fml::RefPtr<fml::TaskRunner>)uiTaskRunner {
|
||||
FML_DCHECK(_shell);
|
||||
return _shell->GetTaskRunners().GetUITaskRunner();
|
||||
}
|
||||
|
||||
- (fml::RefPtr<fml::TaskRunner>)rasterTaskRunner {
|
||||
- (fml::RefPtr<fml::TaskRunner>)RasterTaskRunner {
|
||||
FML_DCHECK(_shell);
|
||||
return _shell->GetTaskRunners().GetRasterTaskRunner();
|
||||
}
|
||||
|
||||
@@ -39,8 +39,7 @@ extern NSString* const kFlutterEngineWillDealloc;
|
||||
- (void)dispatchPointerDataPacket:(std::unique_ptr<flutter::PointerDataPacket>)packet;
|
||||
|
||||
- (fml::RefPtr<fml::TaskRunner>)platformTaskRunner;
|
||||
- (fml::RefPtr<fml::TaskRunner>)uiTaskRunner;
|
||||
- (fml::RefPtr<fml::TaskRunner>)rasterTaskRunner;
|
||||
- (fml::RefPtr<fml::TaskRunner>)RasterTaskRunner;
|
||||
|
||||
- (fml::WeakPtr<flutter::PlatformView>)platformView;
|
||||
|
||||
|
||||
@@ -69,11 +69,11 @@ typedef struct MouseState {
|
||||
/**
|
||||
* Keyboard animation properties
|
||||
*/
|
||||
@property(nonatomic, assign) CGFloat targetViewInsetBottom;
|
||||
@property(nonatomic, assign) CGFloat originalViewInsetBottom;
|
||||
@property(nonatomic, assign) double targetViewInsetBottom;
|
||||
@property(nonatomic, retain) VSyncClient* keyboardAnimationVSyncClient;
|
||||
@property(nonatomic, assign) BOOL keyboardAnimationIsShowing;
|
||||
@property(nonatomic, assign) fml::TimePoint keyboardAnimationStartTime;
|
||||
@property(nonatomic, assign) CGFloat originalViewInsetBottom;
|
||||
@property(nonatomic, assign) BOOL isKeyboardInOrTransitioningFromBackground;
|
||||
|
||||
/// VSyncClient for touch events delivery frame rate correction.
|
||||
@@ -575,8 +575,8 @@ static void SendFakeTouchEvent(FlutterEngine* engine,
|
||||
// Start on the platform thread.
|
||||
weakPlatformView->SetNextFrameCallback([weakSelf = [self getWeakPtr],
|
||||
platformTaskRunner = [_engine.get() platformTaskRunner],
|
||||
rasterTaskRunner = [_engine.get() rasterTaskRunner]]() {
|
||||
FML_DCHECK(rasterTaskRunner->RunsTasksOnCurrentThread());
|
||||
RasterTaskRunner = [_engine.get() RasterTaskRunner]]() {
|
||||
FML_DCHECK(RasterTaskRunner->RunsTasksOnCurrentThread());
|
||||
// Get callback on raster thread and jump back to platform thread.
|
||||
platformTaskRunner->PostTask([weakSelf]() {
|
||||
if (weakSelf) {
|
||||
@@ -1605,55 +1605,7 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
|
||||
|
||||
// Invalidate old vsync client if old animation is not completed.
|
||||
[self invalidateKeyboardAnimationVSyncClient];
|
||||
|
||||
fml::WeakPtr<FlutterViewController> weakSelf = [self getWeakPtr];
|
||||
FlutterKeyboardAnimationCallback keyboardAnimationCallback = ^(
|
||||
fml::TimePoint keyboardAnimationTargetTime) {
|
||||
if (!weakSelf) {
|
||||
return;
|
||||
}
|
||||
fml::scoped_nsobject<FlutterViewController> flutterViewController(
|
||||
[(FlutterViewController*)weakSelf.get() retain]);
|
||||
if (!flutterViewController) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the view controller's view is not loaded, bail out.
|
||||
if (!flutterViewController.get().isViewLoaded) {
|
||||
return;
|
||||
}
|
||||
// If the view for tracking keyboard animation is nil, means it is not
|
||||
// created, bail out.
|
||||
if ([flutterViewController keyboardAnimationView] == nil) {
|
||||
return;
|
||||
}
|
||||
// If keyboardAnimationVSyncClient is nil, means the animation ends.
|
||||
// And should bail out.
|
||||
if (flutterViewController.get().keyboardAnimationVSyncClient == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ([flutterViewController keyboardAnimationView].superview == nil) {
|
||||
// Ensure the keyboardAnimationView is in view hierarchy when animation running.
|
||||
[flutterViewController.get().view addSubview:[flutterViewController keyboardAnimationView]];
|
||||
}
|
||||
|
||||
if ([flutterViewController keyboardSpringAnimation] == nil) {
|
||||
if (flutterViewController.get().keyboardAnimationView.layer.presentationLayer) {
|
||||
flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
|
||||
flutterViewController.get()
|
||||
.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
|
||||
[flutterViewController updateViewportMetricsIfNeeded];
|
||||
}
|
||||
} else {
|
||||
fml::TimeDelta timeElapsed =
|
||||
keyboardAnimationTargetTime - flutterViewController.get().keyboardAnimationStartTime;
|
||||
flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
|
||||
[[flutterViewController keyboardSpringAnimation] curveFunction:timeElapsed.ToSecondsF()];
|
||||
[flutterViewController updateViewportMetricsIfNeeded];
|
||||
}
|
||||
};
|
||||
[self setupKeyboardAnimationVsyncClient:keyboardAnimationCallback];
|
||||
[self setupKeyboardAnimationVsyncClient];
|
||||
VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient;
|
||||
|
||||
[UIView animateWithDuration:duration
|
||||
@@ -1696,28 +1648,45 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
|
||||
toValue:self.targetViewInsetBottom]);
|
||||
}
|
||||
|
||||
- (void)setupKeyboardAnimationVsyncClient:
|
||||
(FlutterKeyboardAnimationCallback)keyboardAnimationCallback {
|
||||
if (!keyboardAnimationCallback) {
|
||||
return;
|
||||
}
|
||||
- (void)setupKeyboardAnimationVsyncClient {
|
||||
auto callback = [weakSelf =
|
||||
[self getWeakPtr]](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
|
||||
if (!weakSelf) {
|
||||
return;
|
||||
}
|
||||
fml::scoped_nsobject<FlutterViewController> flutterViewController(
|
||||
[(FlutterViewController*)weakSelf.get() retain]);
|
||||
if (!flutterViewController) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ([flutterViewController keyboardAnimationView].superview == nil) {
|
||||
// Ensure the keyboardAnimationView is in view hierarchy when animation running.
|
||||
[flutterViewController.get().view addSubview:[flutterViewController keyboardAnimationView]];
|
||||
}
|
||||
|
||||
if ([flutterViewController keyboardSpringAnimation] == nil) {
|
||||
if (flutterViewController.get().keyboardAnimationView.layer.presentationLayer) {
|
||||
flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
|
||||
flutterViewController.get()
|
||||
.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
|
||||
[flutterViewController updateViewportMetricsIfNeeded];
|
||||
}
|
||||
} else {
|
||||
fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() -
|
||||
flutterViewController.get().keyboardAnimationStartTime;
|
||||
|
||||
flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
|
||||
[[flutterViewController keyboardSpringAnimation] curveFunction:timeElapsed.ToSecondsF()];
|
||||
[flutterViewController updateViewportMetricsIfNeeded];
|
||||
}
|
||||
};
|
||||
flutter::Shell& shell = [_engine.get() shell];
|
||||
NSAssert(_keyboardAnimationVSyncClient == nil,
|
||||
@"_keyboardAnimationVSyncClient must be nil when setup");
|
||||
|
||||
// Make sure the new viewport metrics get sent after the begin frame event has processed.
|
||||
fml::scoped_nsprotocol<FlutterKeyboardAnimationCallback> animationCallback(
|
||||
[keyboardAnimationCallback copy]);
|
||||
auto uiCallback = [animationCallback,
|
||||
engine = _engine](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
|
||||
fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime();
|
||||
fml::TimePoint keyboardAnimationTargetTime = recorder->GetVsyncTargetTime() + frameInterval;
|
||||
[engine platformTaskRunner]->PostTask([animationCallback, keyboardAnimationTargetTime] {
|
||||
animationCallback.get()(keyboardAnimationTargetTime);
|
||||
});
|
||||
};
|
||||
|
||||
_keyboardAnimationVSyncClient = [[VSyncClient alloc] initWithTaskRunner:[_engine uiTaskRunner]
|
||||
callback:uiCallback];
|
||||
_keyboardAnimationVSyncClient =
|
||||
[[VSyncClient alloc] initWithTaskRunner:shell.GetTaskRunners().GetPlatformTaskRunner()
|
||||
callback:callback];
|
||||
_keyboardAnimationVSyncClient.allowPauseAfterVsync = NO;
|
||||
[_keyboardAnimationVSyncClient await];
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ FLUTTER_ASSERT_ARC
|
||||
- (void)sendKeyEvent:(const FlutterKeyEvent&)event
|
||||
callback:(nullable FlutterKeyEventCallback)callback
|
||||
userData:(nullable void*)userData;
|
||||
- (fml::RefPtr<fml::TaskRunner>)uiTaskRunner;
|
||||
@end
|
||||
|
||||
/// Sometimes we have to use a custom mock to avoid retain cycles in OCMock.
|
||||
@@ -136,11 +135,10 @@ extern NSNotificationName const FlutterViewControllerWillDealloc;
|
||||
- (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification;
|
||||
- (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame;
|
||||
- (void)startKeyBoardAnimation:(NSTimeInterval)duration;
|
||||
- (void)setupKeyboardAnimationVsyncClient;
|
||||
- (UIView*)keyboardAnimationView;
|
||||
- (SpringAnimation*)keyboardSpringAnimation;
|
||||
- (void)setupKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation;
|
||||
- (void)setupKeyboardAnimationVsyncClient:
|
||||
(FlutterKeyboardAnimationCallback)keyboardAnimationCallback;
|
||||
- (void)ensureViewportMetricsIsCorrect;
|
||||
- (void)invalidateKeyboardAnimationVSyncClient;
|
||||
- (void)addInternalPlugins;
|
||||
@@ -199,6 +197,18 @@ extern NSNotificationName const FlutterViewControllerWillDealloc;
|
||||
OCMVerify([viewControllerMock createTouchRateCorrectionVSyncClientIfNeeded]);
|
||||
}
|
||||
|
||||
- (void)testStartKeyboardAnimationWillInvokeSetupKeyboardAnimationVsyncClient {
|
||||
FlutterEngine* engine = [[FlutterEngine alloc] init];
|
||||
[engine runWithEntrypoint:nil];
|
||||
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
|
||||
nibName:nil
|
||||
bundle:nil];
|
||||
FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
|
||||
viewControllerMock.targetViewInsetBottom = 100;
|
||||
[viewControllerMock startKeyBoardAnimation:0.25];
|
||||
OCMVerify([viewControllerMock setupKeyboardAnimationVsyncClient]);
|
||||
}
|
||||
|
||||
- (void)testStartKeyboardAnimationWillInvokeSetupKeyboardSpringAnimationIfNeeded {
|
||||
FlutterEngine* engine = [[FlutterEngine alloc] init];
|
||||
[engine runWithEntrypoint:nil];
|
||||
@@ -441,34 +451,6 @@ extern NSNotificationName const FlutterViewControllerWillDealloc;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testKeyboardAnimationWillWaitUIThreadVsync {
|
||||
// We need to make sure the new viewport metrics get sent after the
|
||||
// begin frame event has processed. And this test is to expect that the callback
|
||||
// will sync with UI thread. So just simulate a lot of works on UI thread and
|
||||
// test the keyboard animation callback will execute until UI task completed.
|
||||
// Related issue: https://github.com/flutter/flutter/issues/120555.
|
||||
|
||||
FlutterEngine* engine = [[FlutterEngine alloc] init];
|
||||
[engine runWithEntrypoint:nil];
|
||||
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
|
||||
nibName:nil
|
||||
bundle:nil];
|
||||
// Post a task to UI thread to block the thread.
|
||||
const int delayTime = 1;
|
||||
[engine uiTaskRunner]->PostTask([] { sleep(delayTime); });
|
||||
XCTestExpectation* expectation = [self expectationWithDescription:@"keyboard animation callback"];
|
||||
|
||||
__block CFTimeInterval fulfillTime;
|
||||
FlutterKeyboardAnimationCallback callback = ^(fml::TimePoint targetTime) {
|
||||
fulfillTime = CACurrentMediaTime();
|
||||
[expectation fulfill];
|
||||
};
|
||||
CFTimeInterval startTime = CACurrentMediaTime();
|
||||
[viewController setupKeyboardAnimationVsyncClient:callback];
|
||||
[self waitForExpectationsWithTimeout:5.0 handler:nil];
|
||||
XCTAssertTrue(fulfillTime - startTime > delayTime);
|
||||
}
|
||||
|
||||
- (void)testCalculateKeyboardAttachMode {
|
||||
FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
|
||||
[mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
|
||||
@@ -647,9 +629,9 @@ extern NSNotificationName const FlutterViewControllerWillDealloc;
|
||||
}
|
||||
|
||||
- (void)testHandleKeyboardNotification {
|
||||
FlutterEngine* engine = [[FlutterEngine alloc] init];
|
||||
[engine runWithEntrypoint:nil];
|
||||
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
|
||||
FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
|
||||
[mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
|
||||
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
|
||||
nibName:nil
|
||||
bundle:nil];
|
||||
// keyboard is empty
|
||||
@@ -670,9 +652,11 @@ extern NSNotificationName const FlutterViewControllerWillDealloc;
|
||||
[self setupMockMainScreenAndView:viewControllerMock viewFrame:viewFrame convertedFrame:viewFrame];
|
||||
viewControllerMock.targetViewInsetBottom = 0;
|
||||
XCTestExpectation* expectation = [self expectationWithDescription:@"update viewport"];
|
||||
OCMStub([viewControllerMock updateViewportMetricsIfNeeded]).andDo(^(NSInvocation* invocation) {
|
||||
[expectation fulfill];
|
||||
});
|
||||
OCMStub([mockEngine updateViewportMetrics:flutter::ViewportMetrics()])
|
||||
.ignoringNonObjectArgs()
|
||||
.andDo(^(NSInvocation* invocation) {
|
||||
[expectation fulfill];
|
||||
});
|
||||
|
||||
[viewControllerMock handleKeyboardNotification:notification];
|
||||
XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 320 * UIScreen.mainScreen.scale);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h"
|
||||
|
||||
FLUTTER_ASSERT_NOT_ARC
|
||||
@@ -32,9 +31,7 @@ FLUTTER_ASSERT_NOT_ARC
|
||||
@property(nonatomic, retain) VSyncClient* touchRateCorrectionVSyncClient;
|
||||
|
||||
- (void)createTouchRateCorrectionVSyncClientIfNeeded;
|
||||
- (void)setupKeyboardAnimationVsyncClient:
|
||||
(FlutterKeyboardAnimationCallback)keyboardAnimationCallback;
|
||||
- (void)startKeyBoardAnimation:(NSTimeInterval)duration;
|
||||
- (void)setupKeyboardAnimationVsyncClient;
|
||||
- (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches;
|
||||
|
||||
@end
|
||||
@@ -56,9 +53,7 @@ FLUTTER_ASSERT_NOT_ARC
|
||||
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
|
||||
nibName:nil
|
||||
bundle:nil];
|
||||
FlutterKeyboardAnimationCallback callback = ^(fml::TimePoint targetTime) {
|
||||
};
|
||||
[viewController setupKeyboardAnimationVsyncClient:callback];
|
||||
[viewController setupKeyboardAnimationVsyncClient];
|
||||
XCTAssertNotNil(viewController.keyboardAnimationVSyncClient);
|
||||
CADisplayLink* link = [viewController.keyboardAnimationVSyncClient getDisplayLink];
|
||||
XCTAssertNotNil(link);
|
||||
@@ -178,26 +173,4 @@ FLUTTER_ASSERT_NOT_ARC
|
||||
XCTAssertFalse(link.isPaused);
|
||||
}
|
||||
|
||||
- (void)testFlutterViewControllerStartKeyboardAnimationWillCreateVsyncClientCorrectly {
|
||||
FlutterEngine* engine = [[FlutterEngine alloc] init];
|
||||
[engine runWithEntrypoint:nil];
|
||||
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
|
||||
nibName:nil
|
||||
bundle:nil];
|
||||
viewController.targetViewInsetBottom = 100;
|
||||
[viewController startKeyBoardAnimation:0.25];
|
||||
XCTAssertNotNil(viewController.keyboardAnimationVSyncClient);
|
||||
}
|
||||
|
||||
- (void)
|
||||
testSetupKeyboardAnimationVsyncClientWillNotCreateNewVsyncClientWhenKeyboardAnimationCallbackIsNil {
|
||||
FlutterEngine* engine = [[FlutterEngine alloc] init];
|
||||
[engine runWithEntrypoint:nil];
|
||||
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
|
||||
nibName:nil
|
||||
bundle:nil];
|
||||
[viewController setupKeyboardAnimationVsyncClient:nil];
|
||||
XCTAssertNil(viewController.keyboardAnimationVSyncClient);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -33,8 +33,6 @@ typedef NS_ENUM(NSInteger, FlutterKeyboardMode) {
|
||||
FlutterKeyboardModeFloating = 2,
|
||||
};
|
||||
|
||||
typedef void (^FlutterKeyboardAnimationCallback)(fml::TimePoint);
|
||||
|
||||
@interface FlutterViewController () <FlutterViewResponder>
|
||||
|
||||
@property(class, nonatomic, readonly) BOOL accessibilityIsOnOffSwitchLabelsEnabled;
|
||||
|
||||
Reference in New Issue
Block a user