diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 076e3bec3402b..9bcbcff388a47 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -39,6 +39,7 @@ @implementation FlutterViewController { BOOL _initialized; BOOL _viewOpaque; BOOL _engineNeedsLaunch; + NSMutableSet* _ongoingTouches; } #pragma mark - Manage and override all designated initializers @@ -54,6 +55,7 @@ - (instancetype)initWithEngine:(FlutterEngine*)engine _engineNeedsLaunch = NO; _flutterView.reset([[FlutterView alloc] initWithDelegate:_engine opaque:self.isViewOpaque]); _weakFactory = std::make_unique>(self); + _ongoingTouches = [[NSMutableSet alloc] init]; [self performCommonViewControllerInitialization]; [engine setViewController:self]; @@ -75,6 +77,7 @@ - (instancetype)initWithProject:(FlutterDartProject*)projectOrNil _flutterView.reset([[FlutterView alloc] initWithDelegate:_engine opaque:self.isViewOpaque]); [_engine.get() createShell:nil libraryURI:nil]; _engineNeedsLaunch = YES; + _ongoingTouches = [[NSMutableSet alloc] init]; [self loadDefaultSplashScreenView]; [self performCommonViewControllerInitialization]; } @@ -427,9 +430,44 @@ - (void)viewDidDisappear:(BOOL)animated { TRACE_EVENT0("flutter", "viewDidDisappear"); [self surfaceUpdated:NO]; [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.paused"]; + [self flushOngoingTouches]; + [super viewDidDisappear:animated]; } +- (void)flushOngoingTouches { + if (_ongoingTouches.count > 0) { + auto packet = std::make_unique(_ongoingTouches.count); + size_t pointer_index = 0; + // If the view controller is going away, we want to flush cancel all the ongoing + // touches to the framework so nothing gets orphaned. + for (NSNumber* device in _ongoingTouches) { + // Create fake PointerData to balance out each previously started one for the framework. + blink::PointerData pointer_data; + pointer_data.Clear(); + + constexpr int kMicrosecondsPerSecond = 1000 * 1000; + // Use current time. + pointer_data.time_stamp = [[NSDate date] timeIntervalSince1970] * kMicrosecondsPerSecond; + + pointer_data.change = blink::PointerData::Change::kCancel; + pointer_data.kind = blink::PointerData::DeviceKind::kTouch; + pointer_data.device = device.longLongValue; + + // Anything we put here will be arbitrary since there are no touches. + pointer_data.physical_x = 0; + pointer_data.physical_y = 0; + pointer_data.pressure = 1.0; + pointer_data.pressure_max = 1.0; + + packet->SetPointerData(pointer_index++, pointer_data); + } + + [_ongoingTouches removeAllObjects]; + [_engine.get() dispatchPointerDataPacket:std::move(packet)]; + } +} + - (void)dealloc { [_engine.get() notifyViewControllerDeallocated]; [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -528,6 +566,27 @@ - (void)dispatchTouches:(NSSet*)touches pointer_data.physical_x = windowCoordinates.x * scale; pointer_data.physical_y = windowCoordinates.y * scale; + NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device]; + // Track touches that began and not yet stopped so we can flush them + // if the view controller goes away. + switch (pointer_data.change) { + case blink::PointerData::Change::kDown: + [_ongoingTouches addObject:deviceKey]; + break; + case blink::PointerData::Change::kCancel: + case blink::PointerData::Change::kUp: + [_ongoingTouches removeObject:deviceKey]; + break; + case blink::PointerData::Change::kHover: + case blink::PointerData::Change::kMove: + // We're only tracking starts and stops. + break; + case blink::PointerData::Change::kAdd: + case blink::PointerData::Change::kRemove: + // We don't use kAdd/kRemove. + break; + } + // pressure_min is always 0.0 if (@available(iOS 9, *)) { // These properties were introduced in iOS 9.0.