Diff
Modified: trunk/LayoutTests/ChangeLog (285389 => 285390)
--- trunk/LayoutTests/ChangeLog 2021-11-07 04:07:44 UTC (rev 285389)
+++ trunk/LayoutTests/ChangeLog 2021-11-07 16:07:18 UTC (rev 285390)
@@ -1,3 +1,24 @@
+2021-11-07 Simon Fraser <[email protected]>
+
+ Implement UIScriptController.sendEventStream() on macOS for wheel events
+ https://bugs.webkit.org/show_bug.cgi?id=232794
+
+ Reviewed by Tim Horton.
+
+ eventSender.mouseScrollByWithWheelAndMomentumPhases() is problematic for timing-sensitive
+ scrolling behaviors because the timestamps used are not under script control. It's also
+ a rather unwieldy API.
+
+ To make wheel event testing easier, implement enough of UIScriptController.sendEventStream()
+ on macOS that it can generate a series of wheel events with predicable timestamps
+ (currently hardcoded to be 16.6ms apart).
+
+ * fast/scrolling/mac/momentum-event-sequence-expected.txt: Added.
+ * fast/scrolling/mac/momentum-event-sequence.html: Added.
+ * resources/ui-helper.js:
+ (window.UIHelper.async mouseWheelSequence.await.new.Promise):
+ (window.UIHelper.async mouseWheelSequence):
+
2021-11-06 Tyler Wilcock <[email protected]>
AX: WebKit1 PluginViewBase objects with an associated widget()->platformWidget() should be considered attachments
Added: trunk/LayoutTests/fast/scrolling/mac/momentum-event-sequence-expected.txt (0 => 285390)
--- trunk/LayoutTests/fast/scrolling/mac/momentum-event-sequence-expected.txt (rev 0)
+++ trunk/LayoutTests/fast/scrolling/mac/momentum-event-sequence-expected.txt 2021-11-07 16:07:18 UTC (rev 285390)
@@ -0,0 +1,5 @@
+PASS window.scrollY is 46
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/fast/scrolling/mac/momentum-event-sequence.html (0 => 285390)
--- trunk/LayoutTests/fast/scrolling/mac/momentum-event-sequence.html (rev 0)
+++ trunk/LayoutTests/fast/scrolling/mac/momentum-event-sequence.html 2021-11-07 16:07:18 UTC (rev 285390)
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ body {
+ height: 2000px;
+ width: 200%;
+ }
+ </style>
+ <script src=""
+ <script src=""
+ <script>
+ var jsTestIsAsync = true;
+
+ async function testEventSequence()
+ {
+ const wheelEventSquence = {
+ "events" : [
+ {
+ "type" : "wheel",
+ "viewX" : 100,
+ "viewY" : 100,
+ "deltaX" : 0,
+ "deltaY" : -10, // Note that this delta is currently ignored.
+ "phase" : "began"
+ },
+ {
+ "type" : "wheel",
+ "deltaX" : 0,
+ "deltaY" : -10,
+ "phase" : "changed"
+ },
+ {
+ "type" : "wheel",
+ "deltaX" : 0,
+ "deltaY" : -8,
+ "phase" : "changed"
+ },
+ {
+ "type" : "wheel",
+ "deltaX" : 0,
+ "deltaY" : 0,
+ "phase" : "ended"
+ },
+ {
+ "type" : "wheel",
+ "deltaX" : 0,
+ "deltaY" : -10,
+ "momentumPhase" : "began"
+ },
+ {
+ "type" : "wheel",
+ "deltaX" : 0,
+ "deltaY" : -12,
+ "momentumPhase" : "changed"
+ },
+ {
+ "type" : "wheel",
+ "deltaX" : 0,
+ "deltaY" : -6,
+ "momentumPhase" : "changed"
+ },
+ {
+ "type" : "wheel",
+ "momentumPhase" : "ended"
+ }
+ ]
+ };
+
+ await UIHelper.mouseWheelSequence(wheelEventSquence);
+ shouldBe('window.scrollY', '46');
+ }
+
+ async function scrollTest()
+ {
+ await testEventSequence();
+ finishJSTest();
+ }
+
+ window.addEventListener('load', () => {
+ setTimeout(scrollTest, 0);
+ }, false);
+ </script>
+</head>
+<body>
+ <script src=""
+</body>
+</html>
Modified: trunk/LayoutTests/resources/ui-helper.js (285389 => 285390)
--- trunk/LayoutTests/resources/ui-helper.js 2021-11-07 04:07:44 UTC (rev 285389)
+++ trunk/LayoutTests/resources/ui-helper.js 2021-11-07 16:07:18 UTC (rev 285390)
@@ -108,6 +108,28 @@
await UIHelper.animationFrame();
}
+ static async mouseWheelSequence(eventStream)
+ {
+ if (!this.isWebKit2()) {
+ console.log('UIHelper.mouseWheelSequence() does not work in DumpRenderTree')
+ return Promise.resolve();
+ }
+
+ eventSender.monitorWheelEvents();
+ const eventStreamAsString = JSON.stringify(eventStream);
+ await new Promise(resolve => {
+ testRunner.runUIScript(`
+ (function() {
+ uiController.sendEventStream(\`${eventStreamAsString}\`, () => {
+ uiController.uiScriptComplete();
+ });
+ })();
+ `, resolve);
+ });
+
+ await UIHelper.waitForScrollCompletion();
+ }
+
static async waitForScrollCompletion()
{
return new Promise(resolve => {
Modified: trunk/Tools/ChangeLog (285389 => 285390)
--- trunk/Tools/ChangeLog 2021-11-07 04:07:44 UTC (rev 285389)
+++ trunk/Tools/ChangeLog 2021-11-07 16:07:18 UTC (rev 285390)
@@ -1,3 +1,28 @@
+2021-11-07 Simon Fraser <[email protected]>
+
+ Implement UIScriptController.sendEventStream() on macOS for wheel events
+ https://bugs.webkit.org/show_bug.cgi?id=232794
+
+ Reviewed by Tim Horton.
+
+ eventSender.mouseScrollByWithWheelAndMomentumPhases() is problematic for timing-sensitive
+ scrolling behaviors because the timestamps used are not under script control. It's also
+ a rather unwieldy API.
+
+ To make wheel event testing easier, implement enough of UIScriptController.sendEventStream()
+ on macOS that it can generate a series of wheel events with predicable timestamps
+ (currently hardcoded to be 16.6ms apart).
+
+ * WebKitTestRunner/EventSenderProxy.h:
+ * WebKitTestRunner/mac/EventSenderProxy.mm:
+ (WTR::cgScrollPhaseFromPhase):
+ (WTR::cgGesturePhaseFromString):
+ (WTR::EventSenderProxy::sendWheelEvent):
+ * WebKitTestRunner/mac/UIScriptControllerMac.h:
+ * WebKitTestRunner/mac/UIScriptControllerMac.mm:
+ (WTR::eventPhaseFromString):
+ (WTR::UIScriptControllerMac::sendEventStream):
+
2021-11-06 David Kilzer <[email protected]>
Remove empty directories from from svn.webkit.org repository as of r285357
Modified: trunk/Tools/WebKitTestRunner/EventSenderProxy.h (285389 => 285390)
--- trunk/Tools/WebKitTestRunner/EventSenderProxy.h 2021-11-07 04:07:44 UTC (rev 285389)
+++ trunk/Tools/WebKitTestRunner/EventSenderProxy.h 2021-11-07 16:07:18 UTC (rev 285390)
@@ -60,6 +60,8 @@
void mouseForceClick();
void startAndCancelMouseForceClick();
void mouseMoveTo(double x, double y, WKStringRef pointerType = nullptr);
+
+ // Legacy wheel events.
void mouseScrollBy(int x, int y);
void mouseScrollByWithWheelAndMomentumPhases(int x, int y, int phase, int momentum);
#if PLATFORM(GTK)
@@ -67,6 +69,21 @@
#endif
void continuousMouseScrollBy(int x, int y, bool paged);
+#if PLATFORM(MAC)
+ enum class WheelEventPhase : uint8_t {
+ None,
+ Began,
+ Changed,
+ Ended,
+ Cancelled,
+ MayBegin,
+ };
+
+ using EventTimestamp = uint64_t; // mach_absolute_time units.
+
+ void sendWheelEvent(EventTimestamp, double globalX, double globalY, double deltaX, double deltaY, WheelEventPhase, WheelEventPhase momentumPhase);
+#endif
+
void leapForward(int milliseconds);
void keyDown(WKStringRef key, WKEventModifiers, unsigned location);
Modified: trunk/Tools/WebKitTestRunner/mac/EventSenderProxy.mm (285389 => 285390)
--- trunk/Tools/WebKitTestRunner/mac/EventSenderProxy.mm 2021-11-07 04:07:44 UTC (rev 285389)
+++ trunk/Tools/WebKitTestRunner/mac/EventSenderProxy.mm 2021-11-07 16:07:18 UTC (rev 285390)
@@ -753,6 +753,76 @@
}
}
+
+static CGScrollPhase cgScrollPhaseFromPhase(EventSenderProxy::WheelEventPhase phase)
+{
+ switch (phase) {
+ case EventSenderProxy::WheelEventPhase::None:
+ return static_cast<CGScrollPhase>(0);
+ case EventSenderProxy::WheelEventPhase::Began:
+ return kCGScrollPhaseBegan;
+ case EventSenderProxy::WheelEventPhase::Changed:
+ return kCGScrollPhaseChanged;
+ case EventSenderProxy::WheelEventPhase::Ended:
+ return kCGScrollPhaseEnded;
+ case EventSenderProxy::WheelEventPhase::Cancelled:
+ return kCGScrollPhaseCancelled;
+ case EventSenderProxy::WheelEventPhase::MayBegin:
+ return kCGScrollPhaseMayBegin;
+ }
+ ASSERT_NOT_REACHED();
+ return static_cast<CGScrollPhase>(0);
+}
+
+static CGGesturePhase cgGesturePhaseFromString(EventSenderProxy::WheelEventPhase phase)
+{
+ switch (phase) {
+ case EventSenderProxy::WheelEventPhase::None:
+ return kCGGesturePhaseNone;
+ case EventSenderProxy::WheelEventPhase::Began:
+ return kCGGesturePhaseBegan;
+ case EventSenderProxy::WheelEventPhase::Changed:
+ return kCGGesturePhaseChanged;
+ case EventSenderProxy::WheelEventPhase::Ended:
+ return kCGGesturePhaseEnded;
+ case EventSenderProxy::WheelEventPhase::Cancelled:
+ return kCGGesturePhaseCancelled;
+ case EventSenderProxy::WheelEventPhase::MayBegin:
+ return kCGGesturePhaseMayBegin;
+ }
+
+ ASSERT_NOT_REACHED();
+ return kCGGesturePhaseNone;
+}
+
+void EventSenderProxy::sendWheelEvent(EventTimestamp timestamp, double windowX, double windowY, double deltaX, double deltaY, WheelEventPhase phase, WheelEventPhase momentumPhase)
+{
+ constexpr uint32_t wheelCount = 2;
+ auto cgScrollEvent = adoptCF(CGEventCreateScrollWheelEvent2(nullptr, kCGScrollEventUnitPixel, wheelCount, deltaY, deltaX, 0));
+ CGEventSetTimestamp(cgScrollEvent.get(), timestamp);
+
+ // Set the CGEvent location in flipped coords relative to the first screen, which
+ // compensates for the behavior of +[NSEvent eventWithCGEvent:] when the event has
+ // no associated window. See <rdar://problem/17180591>.
+ CGPoint flippedWindowMousePosition = CGPointMake(windowX, [[[NSScreen screens] objectAtIndex:0] frame].size.height - windowY);
+ CGEventSetLocation(cgScrollEvent.get(), flippedWindowMousePosition);
+
+ CGEventSetIntegerValueField(cgScrollEvent.get(), kCGScrollWheelEventIsContinuous, 1);
+ CGEventSetIntegerValueField(cgScrollEvent.get(), kCGScrollWheelEventScrollPhase, cgScrollPhaseFromPhase(phase));
+ CGEventSetIntegerValueField(cgScrollEvent.get(), kCGScrollWheelEventMomentumPhase, cgGesturePhaseFromString(momentumPhase));
+
+ NSEvent* event = [NSEvent eventWithCGEvent:cgScrollEvent.get()];
+ // Our event should have the correct settings:
+ if (NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[event locationInWindow]]) {
+ [NSApp _setCurrentEvent:event];
+ [targetView scrollWheel:event];
+ [NSApp _setCurrentEvent:nil];
+ } else {
+ NSPoint windowLocation = [event locationInWindow];
+ WTFLogAlways("EventSenderProxy::sendWheelEvent failed to find the target view at %f,%f\n", windowLocation.x, windowLocation.y);
+ }
+}
+
#if ENABLE(MAC_GESTURE_EVENTS)
void EventSenderProxy::scaleGestureStart(double scale)
Modified: trunk/Tools/WebKitTestRunner/mac/UIScriptControllerMac.h (285389 => 285390)
--- trunk/Tools/WebKitTestRunner/mac/UIScriptControllerMac.h 2021-11-07 04:07:44 UTC (rev 285389)
+++ trunk/Tools/WebKitTestRunner/mac/UIScriptControllerMac.h 2021-11-07 16:07:18 UTC (rev 285390)
@@ -63,6 +63,8 @@
void chooseMenuAction(JSStringRef, JSValueRef) override;
void activateAtPoint(long x, long y, JSValueRef callback) override;
+
+ void sendEventStream(JSStringRef, JSValueRef) override;
NSTableView *dataListSuggestionsTableView() const;
};
Modified: trunk/Tools/WebKitTestRunner/mac/UIScriptControllerMac.mm (285389 => 285390)
--- trunk/Tools/WebKitTestRunner/mac/UIScriptControllerMac.mm 2021-11-07 04:07:44 UTC (rev 285389)
+++ trunk/Tools/WebKitTestRunner/mac/UIScriptControllerMac.mm 2021-11-07 16:07:18 UTC (rev 285390)
@@ -44,6 +44,7 @@
#import <_javascript_Core/OpaqueJSString.h>
#import <WebKit/WKWebViewPrivate.h>
#import <WebKit/WKWebViewPrivateForTesting.h>
+#import <mach/mach_time.h>
#import <wtf/BlockPtr.h>
#import <wtf/WorkQueue.h>
@@ -307,4 +308,113 @@
[[LayoutTestSpellChecker checker] setResultsFromJSValue:results inContext:m_context->jsContext()];
}
+static NSString* const TopLevelEventInfoKey = @"events";
+static NSString* const EventTypeKey = @"type";
+static NSString* const ViewRelativeXPositionKey = @"viewX";
+static NSString* const ViewRelativeYPositionKey = @"viewY";
+static NSString* const DeltaXKey = @"deltaX";
+static NSString* const DeltaYKey = @"deltaY";
+static NSString* const PhaseKey = @"phase";
+static NSString* const MomentumPhaseKey = @"momentumPhase";
+
+static EventSenderProxy::WheelEventPhase eventPhaseFromString(NSString *phaseStr)
+{
+ if ([phaseStr isEqualToString:@"began"])
+ return EventSenderProxy::WheelEventPhase::Began;
+ if ([phaseStr isEqualToString:@"changed"])
+ return EventSenderProxy::WheelEventPhase::Changed;
+ if ([phaseStr isEqualToString:@"ended"])
+ return EventSenderProxy::WheelEventPhase::Ended;
+ if ([phaseStr isEqualToString:@"cancelled"])
+ return EventSenderProxy::WheelEventPhase::Cancelled;
+ if ([phaseStr isEqualToString:@"maybegin"])
+ return EventSenderProxy::WheelEventPhase::MayBegin;
+
+ ASSERT_NOT_REACHED();
+ return EventSenderProxy::WheelEventPhase::None;
+}
+
+void UIScriptControllerMac::sendEventStream(JSStringRef eventsJSON, JSValueRef callback)
+{
+ auto* eventSender = TestController::singleton().eventSenderProxy();
+ if (!eventSender) {
+ ASSERT_NOT_REACHED();
+ return;
+ }
+
+ unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
+
+ auto jsonString = eventsJSON->string();
+ auto eventInfo = dynamic_objc_cast<NSDictionary>([NSJSONSerialization JSONObjectWithData:[(NSString *)jsonString dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves error:nil]);
+ if (!eventInfo || ![eventInfo isKindOfClass:[NSDictionary class]]) {
+ WTFLogAlways("JSON is not convertible to a dictionary");
+ return;
+ }
+
+ auto *webView = this->webView();
+
+ double currentViewRelativeX = 0;
+ double currentViewRelativeY = 0;
+
+ constexpr uint64_t nanosecondsPerSecond = 1e9;
+ constexpr uint64_t nanosecondsEventInterval = nanosecondsPerSecond / 60;
+
+ auto currentTime = mach_absolute_time();
+
+ for (NSMutableDictionary *event in eventInfo[TopLevelEventInfoKey]) {
+
+ id eventType = event[EventTypeKey];
+ if (!event[EventTypeKey]) {
+ WTFLogAlways("Missing event type");
+ break;
+ }
+
+ if ([eventType isEqualToString:@"wheel"]) {
+ auto phase = EventSenderProxy::WheelEventPhase::None;
+ auto momentumPhase = EventSenderProxy::WheelEventPhase::None;
+
+ if (!event[PhaseKey] && !event[MomentumPhaseKey]) {
+ WTFLogAlways("Event must specify phase or momentumPhase");
+ break;
+ }
+
+ if (id phaseString = event[PhaseKey])
+ phase = eventPhaseFromString(phaseString);
+
+ if (id phaseString = event[MomentumPhaseKey])
+ momentumPhase = eventPhaseFromString(phaseString);
+
+ ASSERT_IMPLIES(phase == EventSenderProxy::WheelEventPhase::None, momentumPhase != EventSenderProxy::WheelEventPhase::None);
+ ASSERT_IMPLIES(momentumPhase == EventSenderProxy::WheelEventPhase::None, phase != EventSenderProxy::WheelEventPhase::None);
+
+ if (event[ViewRelativeXPositionKey])
+ currentViewRelativeX = [event[ViewRelativeXPositionKey] floatValue];
+
+ if (event[ViewRelativeYPositionKey])
+ currentViewRelativeY = [event[ViewRelativeYPositionKey] floatValue];
+
+ double deltaX = 0;
+ double deltaY = 0;
+
+ if (event[DeltaXKey])
+ deltaX = [event[DeltaXKey] floatValue];
+
+ if (event[DeltaYKey])
+ deltaY = [event[DeltaYKey] floatValue];
+
+ auto windowPoint = [webView convertPoint:CGPointMake(currentViewRelativeX, currentViewRelativeY) toView:nil];
+ eventSender->sendWheelEvent(currentTime, windowPoint.x, windowPoint.y, deltaX, deltaY, phase, momentumPhase);
+ }
+
+ currentTime += nanosecondsEventInterval;
+ }
+
+ WorkQueue::main().dispatch([this, strongThis = Ref { *this }, callbackID] {
+ if (!m_context)
+ return;
+ m_context->asyncTaskComplete(callbackID);
+ });
+}
+
+
} // namespace WTR