[Cocoa] WKCustomProtocolLoader should store a WeakPtr to its LegacyCustomProtocolMana...
[WebKit-https.git] / Source / WebKit / UIProcess / _WKTouchEventGenerator.mm
1 /*
2  * Copyright (C) 2015, 2019 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "_WKTouchEventGenerator.h"
28
29 #if PLATFORM(IOS_FAMILY)
30
31 #import "UIKitSPI.h"
32 #import <mach/mach_time.h>
33 #import <pal/spi/cocoa/IOKitSPI.h>
34 #import <wtf/Assertions.h>
35 #import <wtf/RetainPtr.h>
36 #import <wtf/SoftLinking.h>
37
38 SOFT_LINK_PRIVATE_FRAMEWORK(BackBoardServices)
39 SOFT_LINK(BackBoardServices, BKSHIDEventSetDigitizerInfo, void, (IOHIDEventRef digitizerEvent, uint32_t contextID, uint8_t systemGestureisPossible, uint8_t isSystemGestureStateChangeEvent, CFStringRef displayUUID, CFTimeInterval initialTouchTimestamp, float maxForce), (digitizerEvent, contextID, systemGestureisPossible, isSystemGestureStateChangeEvent, displayUUID, initialTouchTimestamp, maxForce));
40
41 static const NSTimeInterval fingerMoveInterval = 0.016;
42 static const IOHIDFloat defaultMajorRadius = 5;
43 static const IOHIDFloat defaultPathPressure = 0;
44 static const long nanosecondsPerSecond = 1e9;
45
46 NSUInteger const HIDMaxTouchCount = 5;
47
48 static int fingerIdentifiers[HIDMaxTouchCount] = { 2, 3, 4, 5, 1 };
49
50 typedef enum {
51     HandEventNull,
52     HandEventTouched,
53     HandEventMoved,
54     HandEventChordChanged,
55     HandEventLifted,
56     HandEventCanceled,
57 } HandEventType;
58
59 typedef struct {
60     int identifier;
61     CGPoint point;
62     IOHIDFloat pathMajorRadius;
63     IOHIDFloat pathPressure;
64     UInt8 pathProximity;
65 } SyntheticEventDigitizerInfo;
66
67 static CFTimeInterval secondsSinceAbsoluteTime(CFAbsoluteTime startTime)
68 {
69     return CFAbsoluteTimeGetCurrent() - startTime;
70 }
71
72 static double simpleCurveInterpolation(double a, double b, double t)
73 {
74     return a + (b - a) * sin(sin(t * M_PI / 2) * t * M_PI / 2);
75 }
76
77 static CGPoint calculateNextCurveLocation(CGPoint a, CGPoint b, CFTimeInterval t)
78 {
79     return CGPointMake(simpleCurveInterpolation(a.x, b.x, t), simpleCurveInterpolation(a.y, b.y, t));
80 }
81
82 static void delayBetweenMove(int eventIndex, double elapsed)
83 {
84     // Delay next event until expected elapsed time.
85     double delay = (eventIndex * fingerMoveInterval) - elapsed;
86     if (delay > 0) {
87         struct timespec moveDelay = { 0, static_cast<long>(delay * nanosecondsPerSecond) };
88         nanosleep(&moveDelay, NULL);
89     }   
90 }
91
92 // NOTE: this event synthesizer is derived from WebKitTestRunner code.
93 // Compared to that version, this lacks support for stylus event simulation,
94 // event stream, and only single touches are exposed via the touch/lift/move method calls.
95 @interface _WKTouchEventGenerator ()
96 @property (nonatomic, strong) NSMutableDictionary *eventCallbacks;
97 @end
98
99 @implementation _WKTouchEventGenerator {
100     IOHIDEventSystemClientRef _ioSystemClient;
101     SyntheticEventDigitizerInfo _activePoints[HIDMaxTouchCount];
102     NSUInteger _activePointCount;
103 }
104
105 + (_WKTouchEventGenerator *)sharedTouchEventGenerator
106 {
107     static _WKTouchEventGenerator *eventGenerator = [[_WKTouchEventGenerator alloc] init];
108     return eventGenerator;
109 }
110
111 + (CFIndex)nextEventCallbackID
112 {
113     static CFIndex callbackID = 0;
114     return ++callbackID;
115 }
116
117 - (instancetype)init
118 {
119     self = [super init];
120     if (!self)
121         return nil;
122
123     for (NSUInteger i = 0; i < HIDMaxTouchCount; ++i)
124         _activePoints[i].identifier = fingerIdentifiers[i];
125
126     _eventCallbacks = [[NSMutableDictionary alloc] init];
127
128     return self;
129 }
130
131 - (void)dealloc
132 {
133   if (_ioSystemClient)
134         CFRelease(_ioSystemClient);
135
136     [_eventCallbacks release];
137     [super dealloc];
138 }
139
140 - (IOHIDEventRef)_createIOHIDEventType:(HandEventType)eventType
141 {
142     BOOL isTouching = (eventType == HandEventTouched || eventType == HandEventMoved || eventType == HandEventChordChanged);
143
144     IOHIDDigitizerEventMask eventMask = kIOHIDDigitizerEventTouch;
145     if (eventType == HandEventMoved) {
146         eventMask &= ~kIOHIDDigitizerEventTouch;
147         eventMask |= kIOHIDDigitizerEventPosition;
148         eventMask |= kIOHIDDigitizerEventAttribute;
149     } else if (eventType == HandEventChordChanged) {
150         eventMask |= kIOHIDDigitizerEventPosition;
151         eventMask |= kIOHIDDigitizerEventAttribute;
152     } else if (eventType == HandEventTouched || eventType == HandEventCanceled || eventType == HandEventLifted)
153         eventMask |= kIOHIDDigitizerEventIdentity;
154
155     uint64_t machTime = mach_absolute_time();
156     RetainPtr<IOHIDEventRef> eventRef = adoptCF(IOHIDEventCreateDigitizerEvent(kCFAllocatorDefault, machTime,
157         kIOHIDDigitizerTransducerTypeHand,
158         0,
159         0,
160         eventMask,
161         0,
162         0, 0, 0,
163         0,
164         0,
165         0,
166         isTouching,
167         kIOHIDEventOptionNone));
168
169     IOHIDEventSetIntegerValue(eventRef.get(), kIOHIDEventFieldDigitizerIsDisplayIntegrated, 1);
170
171     for (NSUInteger i = 0; i < _activePointCount; ++i) {
172         SyntheticEventDigitizerInfo* pointInfo = &_activePoints[i];
173         if (eventType == HandEventTouched) {
174             if (!pointInfo->pathMajorRadius)
175                 pointInfo->pathMajorRadius = defaultMajorRadius;
176             if (!pointInfo->pathPressure)
177                 pointInfo->pathPressure = defaultPathPressure;
178             if (!pointInfo->pathProximity)
179                 pointInfo->pathProximity = kGSEventPathInfoInTouch | kGSEventPathInfoInRange;
180         } else if (eventType == HandEventLifted || eventType == HandEventCanceled) {
181             pointInfo->pathMajorRadius = 0;
182             pointInfo->pathPressure = 0;
183             pointInfo->pathProximity = 0;
184         }
185
186         CGPoint point = pointInfo->point;
187         point = CGPointMake(roundf(point.x), roundf(point.y));
188         
189         RetainPtr<IOHIDEventRef> subEvent = adoptCF(IOHIDEventCreateDigitizerFingerEvent(kCFAllocatorDefault, machTime,
190             pointInfo->identifier,
191             pointInfo->identifier,
192             eventMask,
193             point.x, point.y, 0,
194             pointInfo->pathPressure,
195             0,
196             pointInfo->pathProximity & kGSEventPathInfoInRange,
197             pointInfo->pathProximity & kGSEventPathInfoInTouch,
198             kIOHIDEventOptionNone));
199
200         IOHIDEventSetFloatValue(subEvent.get(), kIOHIDEventFieldDigitizerMajorRadius, pointInfo->pathMajorRadius);
201         IOHIDEventSetFloatValue(subEvent.get(), kIOHIDEventFieldDigitizerMinorRadius, pointInfo->pathMajorRadius);
202
203         IOHIDEventAppendEvent(eventRef.get(), subEvent.get(), 0);
204     }
205
206     return eventRef.leakRef();
207 }
208
209 - (BOOL)_sendHIDEvent:(IOHIDEventRef)eventRef
210 {
211     if (!_ioSystemClient)
212         _ioSystemClient = IOHIDEventSystemClientCreate(kCFAllocatorDefault);
213
214     if (eventRef) {
215         RetainPtr<IOHIDEventRef> strongEvent = eventRef;
216         dispatch_async(dispatch_get_main_queue(), ^{
217             uint32_t contextID = [UIApplication sharedApplication].keyWindow._contextId;
218             ASSERT(contextID);
219             BKSHIDEventSetDigitizerInfo(strongEvent.get(), contextID, false, false, NULL, 0, 0);
220             [[UIApplication sharedApplication] _enqueueHIDEvent:strongEvent.get()];
221         });
222     }
223     return YES;
224 }
225
226 - (BOOL)_sendMarkerHIDEventWithCompletionBlock:(void (^)(void))completionBlock
227 {
228     auto callbackID = [_WKTouchEventGenerator nextEventCallbackID];
229     [_eventCallbacks setObject:Block_copy(completionBlock) forKey:@(callbackID)];
230
231     auto markerEvent = adoptCF(IOHIDEventCreateVendorDefinedEvent(kCFAllocatorDefault,
232         mach_absolute_time(),
233         kHIDPage_VendorDefinedStart + 100,
234         0,
235         1,
236         (uint8_t*)&callbackID,
237         sizeof(CFIndex),
238         kIOHIDEventOptionNone));
239     
240     if (markerEvent) {
241         dispatch_async(dispatch_get_main_queue(), [markerEvent = WTFMove(markerEvent)] {
242             auto contextID = [UIApplication sharedApplication].keyWindow._contextId;
243             ASSERT(contextID);
244             BKSHIDEventSetDigitizerInfo(markerEvent.get(), contextID, false, false, NULL, 0, 0);
245             [[UIApplication sharedApplication] _enqueueHIDEvent:markerEvent.get()];
246         });
247     }
248     return YES;
249 }
250
251 - (void)_updateTouchPoints:(CGPoint*)points count:(NSUInteger)count
252 {
253     HandEventType handEventType;
254     
255     // The hand event type is based on previous state.
256     if (!_activePointCount)
257         handEventType = HandEventTouched;
258     else if (!count)
259         handEventType = HandEventLifted;
260     else if (count == _activePointCount)
261         handEventType = HandEventMoved;
262     else
263         handEventType = HandEventChordChanged;
264     
265     // Update previous count for next event.
266     _activePointCount = count;
267
268     // Update point locations.
269     for (NSUInteger i = 0; i < count; ++i)
270         _activePoints[i].point = points[i];
271     
272     RetainPtr<IOHIDEventRef> eventRef = adoptCF([self _createIOHIDEventType:handEventType]);
273     [self _sendHIDEvent:eventRef.get()];
274 }
275
276 - (void)touchDownAtPoints:(CGPoint*)locations touchCount:(NSUInteger)touchCount
277 {
278     touchCount = std::min(touchCount, HIDMaxTouchCount);
279
280     _activePointCount = touchCount;
281
282     for (NSUInteger index = 0; index < touchCount; ++index)
283         _activePoints[index].point = locations[index];
284
285     RetainPtr<IOHIDEventRef> eventRef = adoptCF([self _createIOHIDEventType:HandEventTouched]);
286     [self _sendHIDEvent:eventRef.get()];
287 }
288
289 - (void)touchDown:(CGPoint)location touchCount:(NSUInteger)touchCount
290 {
291     touchCount = std::min(touchCount, HIDMaxTouchCount);
292
293     CGPoint locations[touchCount];
294
295     for (NSUInteger index = 0; index < touchCount; ++index)
296         locations[index] = location;
297     
298     [self touchDownAtPoints:locations touchCount:touchCount];
299 }
300
301 - (void)touchDown:(CGPoint)location
302 {
303     [self touchDownAtPoints:&location touchCount:1];
304 }
305
306 - (void)liftUpAtPoints:(CGPoint*)locations touchCount:(NSUInteger)touchCount
307 {
308     touchCount = std::min(touchCount, HIDMaxTouchCount);
309     touchCount = std::min(touchCount, _activePointCount);
310
311     NSUInteger newPointCount = _activePointCount - touchCount;
312
313     for (NSUInteger index = 0; index < touchCount; ++index)
314         _activePoints[newPointCount + index].point = locations[index];
315     
316     RetainPtr<IOHIDEventRef> eventRef = adoptCF([self _createIOHIDEventType:HandEventLifted]);
317     [self _sendHIDEvent:eventRef.get()];
318     
319     _activePointCount = newPointCount;
320 }
321
322 - (void)liftUp:(CGPoint)location touchCount:(NSUInteger)touchCount
323 {
324     touchCount = std::min(touchCount, HIDMaxTouchCount);
325
326     CGPoint locations[touchCount];
327
328     for (NSUInteger index = 0; index < touchCount; ++index)
329         locations[index] = location;
330     
331     [self liftUpAtPoints:locations touchCount:touchCount];
332 }
333
334 - (void)liftUp:(CGPoint)location
335 {
336     [self liftUp:location touchCount:1];
337 }
338
339 - (void)moveToPoints:(CGPoint*)newLocations touchCount:(NSUInteger)touchCount duration:(NSTimeInterval)seconds
340 {
341     touchCount = std::min(touchCount, HIDMaxTouchCount);
342
343     CGPoint startLocations[touchCount];
344     CGPoint nextLocations[touchCount];
345
346     CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
347     CFTimeInterval elapsed = 0;
348
349     int eventIndex = 0;
350     while (elapsed < (seconds - fingerMoveInterval)) {
351         elapsed = secondsSinceAbsoluteTime(startTime);
352         CFTimeInterval interval = elapsed / seconds;
353         
354         for (NSUInteger i = 0; i < touchCount; ++i) {
355             if (!eventIndex)
356                 startLocations[i] = _activePoints[i].point;
357
358             nextLocations[i] = calculateNextCurveLocation(startLocations[i], newLocations[i], interval);
359         }
360         [self _updateTouchPoints:nextLocations count:touchCount];
361
362         delayBetweenMove(eventIndex++, elapsed);
363     }
364
365     [self _updateTouchPoints:newLocations count:touchCount];
366 }
367
368 - (void)touchDown:(CGPoint)location completionBlock:(void (^)(void))completionBlock
369 {
370     [self touchDown:location touchCount:1];
371     [self _sendMarkerHIDEventWithCompletionBlock:completionBlock];
372 }
373
374 - (void)liftUp:(CGPoint)location completionBlock:(void (^)(void))completionBlock
375 {
376     [self liftUp:location touchCount:1];
377     [self _sendMarkerHIDEventWithCompletionBlock:completionBlock];
378 }
379
380 - (void)moveToPoint:(CGPoint)location duration:(NSTimeInterval)seconds completionBlock:(void (^)(void))completionBlock
381 {
382     CGPoint locations[1];
383     locations[0] = location;
384     [self moveToPoints:locations touchCount:1 duration:seconds];
385     [self _sendMarkerHIDEventWithCompletionBlock:completionBlock];
386 }
387
388 - (void)receivedHIDEvent:(IOHIDEventRef)event
389 {
390     if (IOHIDEventGetType(event) != kIOHIDEventTypeVendorDefined)
391         return;
392
393     CFIndex callbackID = IOHIDEventGetIntegerValue(event, kIOHIDEventFieldVendorDefinedData);
394     NSNumber *key = @(callbackID);
395     void (^completionBlock)() = [_eventCallbacks objectForKey:key];
396     if (completionBlock) {
397         [_eventCallbacks removeObjectForKey:key];
398         completionBlock();
399         Block_release(completionBlock);
400     }
401 }
402
403 @end
404
405 #endif // PLATFORM(IOS_FAMILY)