Skip to content

Commit

Permalink
[iOS] Adopt UIAsyncTextInput, UIKeyEvent and related APIs
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=262017
rdar://116319413

Reviewed by Tim Horton.

First pass at adopting new UIKit interfaces for dispatching key events asynchronously and deferring
key events to UIKit in the case where the default action was not prevented by the page. This work
aims to clean up existing architecture for propagating key events from UIKit to WebKit, which
currently requires UIKit to instantiate a `WebEvent` (from the WebCore framework) and hand it to
WebKit. In this new architecture, UIKit instead sends `UIKeyEvent`s to WebKit, which (for the time
being) are wrapped by `WebEvent`s within WebKit code.

This will eventually allow us to break UIKit out of the WebKit "framework layering sandwich",
wherein WebKit links UIKit and UIKit currently links WebKitLegacy in order to call directly into the
WebCore framework.

See below for more details.

* Source/WebCore/WebCore.xcodeproj/project.pbxproj:
* Source/WebCore/platform/ios/WebEvent.mm:
(webEventType):
(webEventModifierFlags):
(-[WebEvent initWithUIKeyEvent:]):
(-[WebEvent originalUIKeyEvent]):

Add an initializer to wrap a `UIKeyEvent` in a `WebEvent`, while keeping a pointer to the original
`UIKeyEvent` (accessible via a new property, `-originalUIKeyEvent`).

* Source/WebCore/platform/ios/WebEventPrivate.h: Added.

Put these new WebCore helpers in a new category on `WebEvent` in a separate private header,
`WebEventPrivate.h`. This separation from `WebEvent.h` allows us to use `HAVE(UI_ASYNC_TEXT_INPUT)`
consistently throughout WebKit, and avoid expanding the API surface exported through `WebEvent` to
system clients, such as UIKit or accessibility.

* Source/WebKit/UIProcess/ios/WKContentViewInteraction.h:
* Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView _interpretKeyEvent:isCharEvent:]):

Call back into UIKit via `-deferHandlingToSystemForKeyEvent:` when interpreting key events.

(-[WKContentView asyncSystemInputDelegate]):
(-[WKContentView setAsyncSystemInputDelegate:]):
(-[WKContentView handleAsyncKeyEvent:withCompletionHandler:]):

Wrap the given `UIKeyEvent` in a `WebEvent`, and just call into the existing `-handleKeyWebEvent:`
method.

(-[WKContentView textInputView]):

Canonical link: https://commits.webkit.org/269010@main
  • Loading branch information
whsieh committed Oct 6, 2023
1 parent 75d236e commit bdc1186
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 4 deletions.
8 changes: 6 additions & 2 deletions Source/WebCore/WebCore.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -6184,6 +6184,7 @@
F49786881FF45FA500E060AB /* PasteboardItemInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = F49786871FF45FA500E060AB /* PasteboardItemInfo.h */; settings = {ATTRIBUTES = (Private, ); }; };
F498C6BF299EE7B200CBA427 /* NoiseInjectionPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = F498C6BE299EE7A400CBA427 /* NoiseInjectionPolicy.h */; };
F4A064C8277E48C900B06A17 /* FontCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = F4A064C6277E48C600B06A17 /* FontCocoa.h */; settings = {ATTRIBUTES = (Private, ); }; };
F4A1C12A2AD05FA400862999 /* WebEventPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = F4A1C1292AD05F8A00862999 /* WebEventPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; };
F4B0018926E7F21F006EAABE /* HTMLNameCache.h in Headers */ = {isa = PBXBuildFile; fileRef = F4B0018726E7F21F006EAABE /* HTMLNameCache.h */; };
F4B2A909265030BA009E7286 /* DataDetectorHighlight.h in Headers */ = {isa = PBXBuildFile; fileRef = F4B2A90626502BA0009E7286 /* DataDetectorHighlight.h */; };
F4B422C4220C0568009E1E7D /* DOMPasteAccess.h in Headers */ = {isa = PBXBuildFile; fileRef = F4B422C2220C0000009E1E7D /* DOMPasteAccess.h */; settings = {ATTRIBUTES = (Private, ); }; };
Expand Down Expand Up @@ -6739,8 +6740,8 @@
073930DF2AC6490B00C1D1B1 /* PhotoCapabilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoCapabilities.h; sourceTree = "<group>"; };
073931142ACC6CEE00C1D1B1 /* PhotoSettings.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = PhotoSettings.idl; sourceTree = "<group>"; };
073931162ACC6D1D00C1D1B1 /* PhotoSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoSettings.h; sourceTree = "<group>"; };
073931182ACCBF3B00C1D1B1 /* JSPhotoSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JSPhotoSettings.h; path = JSPhotoSettings.h; sourceTree = "<group>"; };
073931192ACCBF3B00C1D1B1 /* JSPhotoSettings.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = JSPhotoSettings.cpp; path = JSPhotoSettings.cpp; sourceTree = "<group>"; };
073931182ACCBF3B00C1D1B1 /* JSPhotoSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSPhotoSettings.h; sourceTree = "<group>"; };
073931192ACCBF3B00C1D1B1 /* JSPhotoSettings.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSPhotoSettings.cpp; sourceTree = "<group>"; };
073B87561E40DCE50071C0EC /* AudioStreamDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioStreamDescription.h; sourceTree = "<group>"; };
073B87571E40DCFD0071C0EC /* CAAudioStreamDescription.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CAAudioStreamDescription.cpp; sourceTree = "<group>"; };
073B87581E40DCFD0071C0EC /* CAAudioStreamDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CAAudioStreamDescription.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -19848,6 +19849,7 @@
F498C6BE299EE7A400CBA427 /* NoiseInjectionPolicy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NoiseInjectionPolicy.h; sourceTree = "<group>"; };
F49E98E421DEE6C1009AE55E /* EditAction.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = EditAction.cpp; sourceTree = "<group>"; };
F4A064C6277E48C600B06A17 /* FontCocoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FontCocoa.h; sourceTree = "<group>"; };
F4A1C1292AD05F8A00862999 /* WebEventPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebEventPrivate.h; sourceTree = "<group>"; };
F4B0018726E7F21F006EAABE /* HTMLNameCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTMLNameCache.h; sourceTree = "<group>"; };
F4B0018826E7F21F006EAABE /* HTMLNameCache.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = HTMLNameCache.cpp; sourceTree = "<group>"; };
F4B2A90626502BA0009E7286 /* DataDetectorHighlight.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DataDetectorHighlight.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -29955,6 +29957,7 @@
31403798124BEA7F00AF40E4 /* WebCoreMotionManager.mm */,
FE0D84E810484348001A179E /* WebEvent.h */,
FE0D84EA1048436E001A179E /* WebEvent.mm */,
F4A1C1292AD05F8A00862999 /* WebEventPrivate.h */,
F482230F1E3869B80066FC79 /* WebItemProviderPasteboard.h */,
F482230E1E3869B80066FC79 /* WebItemProviderPasteboard.mm */,
1F8756B11E22BEEF0042C40D /* WebSQLiteDatabaseTrackerClient.h */,
Expand Down Expand Up @@ -42183,6 +42186,7 @@
93F199F008245E59001E9ABC /* WebCoreView.h in Headers */,
A5B81CB61FAA44620037D1E6 /* WebDebuggerAgent.h in Headers */,
FE0D84E910484348001A179E /* WebEvent.h in Headers */,
F4A1C12A2AD05FA400862999 /* WebEventPrivate.h in Headers */,
225A16B50D5C11E900090295 /* WebEventRegion.h in Headers */,
D3F3D36E1A69B7E00059FC2B /* WebGL2RenderingContext.h in Headers */,
A7D20F6D107F438B00A80392 /* WebGLActiveInfo.h in Headers */,
Expand Down
70 changes: 69 additions & 1 deletion Source/WebCore/platform/ios/WebEvent.mm
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@

#import "KeyEventCocoa.h"
#import <wtf/Assertions.h>
#import <wtf/RetainPtr.h>

#if PLATFORM(IOS_FAMILY)

#import "KeyEventCodesIOS.h"
#import "WAKAppKitStubs.h"
#import "WebEventPrivate.h"
#import <pal/ios/UIKitSoftLink.h>
#import <pal/spi/cocoa/IOKitSPI.h>
#import <pal/spi/ios/GraphicsServicesSPI.h>
Expand All @@ -43,7 +45,11 @@
using WebCore::windowsKeyCodeForKeyCode;
using WebCore::windowsKeyCodeForCharCode;

@implementation WebEvent
@implementation WebEvent {
#if HAVE(UI_ASYNC_TEXT_INPUT)
RetainPtr<UIKeyEvent> _originalUIKeyEvent;
#endif
}

@synthesize type = _type;
@synthesize timestamp = _timestamp;
Expand Down Expand Up @@ -487,4 +493,66 @@ + (WebEventFlags)modifierFlags

@end

#if HAVE(UI_ASYNC_TEXT_INPUT)

@implementation WebEvent (UIAsyncTextInputSupport)

static inline WebEventType webEventType(UIKeyEventType type)
{
switch (type) {
case UIKeyEventKeyDown:
return WebEventKeyDown;
case UIKeyEventKeyUp:
return WebEventKeyUp;
}
ASSERT_NOT_REACHED();
return WebEventKeyDown;
}

static inline WebEventFlags webEventModifierFlags(UIKeyModifierFlags flags)
{
WebEventFlags modifiers = 0;
if (flags & UIKeyModifierCommand)
modifiers |= WebEventFlagMaskCommandKey;
if (flags & UIKeyModifierAlternate)
modifiers |= WebEventFlagMaskOptionKey;
if (flags & UIKeyModifierControl)
modifiers |= WebEventFlagMaskControlKey;
if (flags & UIKeyModifierShift)
modifiers |= WebEventFlagMaskShiftKey;
return modifiers;
}

- (instancetype)initWithUIKeyEvent:(UIKeyEvent *)event
{
if (!(self = [super init]))
return nil;

_type = webEventType(event.type);
_timestamp = static_cast<CFTimeInterval>(event.timestamp);
_modifierFlags = webEventModifierFlags(event.modifierFlags);
_keyboardFlags = 0;
// FIXME: Set WebEventKeyboardInputModifierFlagsChanged as needed.
if (event.keyRepeating)
_keyboardFlags |= WebEventKeyboardInputRepeat;

_keyCode = static_cast<uint16_t>(event.keyCode);
_characters = [event.characters retain];
_charactersIgnoringModifiers = [event.charactersIgnoringModifiers retain];
_tabKey = NO; // FIXME: Populate this field appropriately.
_keyRepeating = event.keyRepeating;
_originalUIKeyEvent = event;

return self;
}

- (UIKeyEvent *)originalUIKeyEvent
{
return _originalUIKeyEvent.get();
}

@end

#endif // HAVE(UI_ASYNC_TEXT_INPUT)

#endif // PLATFORM(IOS_FAMILY)
41 changes: 41 additions & 0 deletions Source/WebCore/platform/ios/WebEventPrivate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2023 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/

#pragma once

#import <WebCore/WebEvent.h>

#if HAVE(UI_ASYNC_TEXT_INPUT)

@class UIKeyEvent;

@interface WebEvent (UIAsyncTextInputSupport)

- (instancetype)initWithUIKeyEvent:(UIKeyEvent *)event;
@property (nonatomic, readonly) UIKeyEvent *originalUIKeyEvent;

@end

#endif // HAVE(UI_ASYNC_TEXT_INPUT)
6 changes: 6 additions & 0 deletions Source/WebKit/UIProcess/ios/WKContentViewInteraction.h
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,9 @@ struct ImageAnalysisContextMenuActionData {
WebCore::FloatRect _imageAnalysisInteractionBounds;
std::optional<WebKit::RemoveBackgroundData> _removeBackgroundData;
#endif
#if HAVE(UI_ASYNC_TEXT_INPUT)
__weak id<UIAsyncTextInputDelegate> _asyncSystemInputDelegate;
#endif
}

@end
Expand All @@ -576,6 +579,9 @@ struct ImageAnalysisContextMenuActionData {
#if HAVE(UI_ASYNC_DRAG_INTERACTION)
, _UIAsyncDragInteractionDelegate
#endif
#if HAVE(UI_ASYNC_TEXT_INPUT)
, UIAsyncTextInput
#endif
>

@property (nonatomic, readonly) CGPoint lastInteractionLocation;
Expand Down
40 changes: 39 additions & 1 deletion Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
#import <WebCore/UTIUtilities.h>
#import <WebCore/VisibleSelection.h>
#import <WebCore/WebCoreCALayerExtras.h>
#import <WebCore/WebEvent.h>
#import <WebCore/WebEventPrivate.h>
#import <WebCore/WebTextIndicatorLayer.h>
#import <WebCore/WritingDirection.h>
#import <WebKit/WebSelectionRect.h> // FIXME: WebKit should not include WebKitLegacy headers!
Expand Down Expand Up @@ -6602,6 +6602,14 @@ - (void)_didHandleKeyEvent:(::WebEvent *)event eventWasHandled:(BOOL)eventWasHan

- (BOOL)_interpretKeyEvent:(::WebEvent *)event isCharEvent:(BOOL)isCharEvent
{
#if HAVE(UI_ASYNC_TEXT_INPUT)
if (auto systemDelegate = retainPtr(_asyncSystemInputDelegate)) {
auto originalKeyEvent = retainPtr(event.originalUIKeyEvent);
ASSERT(originalKeyEvent);
return [systemDelegate deferHandlingToSystemForKeyEvent:originalKeyEvent.get()];
}
#endif

if (event.keyboardFlags & WebEventKeyboardInputModifierFlagsChanged)
return NO;

Expand Down Expand Up @@ -11800,6 +11808,36 @@ - (void)willDismissEditMenuWithAnimator:(id<UIEditMenuInteractionAnimating>)anim

#endif // HAVE(UI_EDIT_MENU_INTERACTION)

#if HAVE(UI_ASYNC_TEXT_INPUT)

#pragma mark - UIAsyncTextInput (and related)

- (id<UIAsyncTextInputDelegate>)asyncSystemInputDelegate
{
return _asyncSystemInputDelegate;
}

- (void)setAsyncSystemInputDelegate:(id<UIAsyncTextInputDelegate>)delegate
{
_asyncSystemInputDelegate = delegate;
}

- (void)handleAsyncKeyEvent:(UIKeyEvent *)event withCompletionHandler:(void(^)(UIKeyEvent *, BOOL))completionHandler
{
auto webEvent = adoptNS([[::WebEvent alloc] initWithUIKeyEvent:event]);
[self handleKeyWebEvent:webEvent.get() withCompletionHandler:[originalEvent = retainPtr(event), completionHandler = makeBlockPtr(completionHandler)](::WebEvent *webEvent, BOOL handled) {
ASSERT(webEvent.originalUIKeyEvent == originalEvent);
completionHandler(originalEvent.get(), handled);
}];
}

- (UIView *)textInputView
{
return self;
}

#endif // HAVE(UI_ASYNC_TEXT_INPUT)

@end

@implementation WKContentView (WKTesting)
Expand Down

0 comments on commit bdc1186

Please sign in to comment.