Cocoa: add private API for suppressing first responder changes
authormrajca@apple.com <mrajca@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 20 Apr 2017 23:42:00 +0000 (23:42 +0000)
committermrajca@apple.com <mrajca@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 20 Apr 2017 23:42:00 +0000 (23:42 +0000)
https://bugs.webkit.org/show_bug.cgi?id=171069
<rdar://problem/31658967>

Reviewed by Tim Horton.

Some clients may not want the web view to be programmatically made the first responder
and invoking -makeFirstResponder: unconditionally posts 'first responder did change'
notifications.

Added API test.

* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _setShouldSuppressFirstResponderChanges:]):
* UIProcess/API/Cocoa/WKWebViewPrivate.h:
* UIProcess/Cocoa/WebViewImpl.h:
* UIProcess/Cocoa/WebViewImpl.mm:
(WebKit::WebViewImpl::setShouldSuppressFirstResponderChanges):
* UIProcess/PageClient.h:
* UIProcess/mac/PageClientImpl.h:
* UIProcess/mac/PageClientImpl.mm:
(WebKit::PageClientImpl::makeFirstResponder):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@215587 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebKit2/ChangeLog
Source/WebKit2/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit2/UIProcess/API/Cocoa/WKWebViewPrivate.h
Source/WebKit2/UIProcess/Cocoa/WebViewImpl.h
Source/WebKit2/UIProcess/Cocoa/WebViewImpl.mm
Source/WebKit2/UIProcess/PageClient.h
Source/WebKit2/UIProcess/mac/PageClientImpl.h
Source/WebKit2/UIProcess/mac/PageClientImpl.mm
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/mac/FirstResponderSuppression.mm [new file with mode: 0644]

index 9f7f7e1..f76836e 100644 (file)
@@ -1,3 +1,28 @@
+2017-04-20  Matt Rajca  <mrajca@apple.com>
+
+        Cocoa: add private API for suppressing first responder changes
+        https://bugs.webkit.org/show_bug.cgi?id=171069
+        <rdar://problem/31658967>
+
+        Reviewed by Tim Horton.
+
+        Some clients may not want the web view to be programmatically made the first responder
+        and invoking -makeFirstResponder: unconditionally posts 'first responder did change'
+        notifications.
+
+        Added API test.
+
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView _setShouldSuppressFirstResponderChanges:]):
+        * UIProcess/API/Cocoa/WKWebViewPrivate.h:
+        * UIProcess/Cocoa/WebViewImpl.h:
+        * UIProcess/Cocoa/WebViewImpl.mm:
+        (WebKit::WebViewImpl::setShouldSuppressFirstResponderChanges):
+        * UIProcess/PageClient.h:
+        * UIProcess/mac/PageClientImpl.h:
+        * UIProcess/mac/PageClientImpl.mm:
+        (WebKit::PageClientImpl::makeFirstResponder):
+
 2017-04-20  Eric Carlson  <eric.carlson@apple.com>
 
         [MediaStream iOS] Hold process assertion while media capture is active
index 17d39bc..c06ba25 100644 (file)
@@ -3729,6 +3729,13 @@ WEBCORE_COMMAND(yankAndSelect)
     _page->terminateProcess();
 }
 
+#if PLATFORM(MAC)
+- (void)_setShouldSuppressFirstResponderChanges:(BOOL)shouldSuppress
+{
+    _impl->setShouldSuppressFirstResponderChanges(shouldSuppress);
+}
+#endif
+
 #if PLATFORM(IOS)
 static WebCore::FloatSize activeMaximumUnobscuredSize(WKWebView *webView, const CGRect& bounds)
 {
index cb82c46..7c7d225 100644 (file)
@@ -216,6 +216,8 @@ typedef NS_ENUM(NSInteger, _WKImmediateActionType) {
 
 @property (nonatomic, setter=_setWindowOcclusionDetectionEnabled:) BOOL _windowOcclusionDetectionEnabled;
 
+- (void)_setShouldSuppressFirstResponderChanges:(BOOL)shouldSuppress;
+
 // Clients that want to maintain default behavior can return nil. To disable the immediate action entirely, return NSNull. And to
 // do something custom, return an object that conforms to the NSImmediateActionAnimationController protocol.
 - (id)_immediateActionAnimationControllerForHitTestResult:(_WKHitTestResult *)hitTestResult withType:(_WKImmediateActionType)type userData:(id<NSSecureCoding>)userData;
index 1ee600f..0b6cbda 100644 (file)
@@ -140,6 +140,7 @@ public:
     bool drawsBackground() const;
     bool isOpaque() const;
 
+    void setShouldSuppressFirstResponderChanges(bool);
     bool acceptsFirstMouse(NSEvent *);
     bool acceptsFirstResponder();
     bool becomeFirstResponder();
index 50e451a..85d392c 100644 (file)
@@ -1365,6 +1365,11 @@ bool WebViewImpl::isOpaque() const
     return m_page->drawsBackground();
 }
 
+void WebViewImpl::setShouldSuppressFirstResponderChanges(bool shouldSuppress)
+{   
+    m_pageClient->setShouldSuppressFirstResponderChanges(shouldSuppress);
+}
+
 bool WebViewImpl::acceptsFirstResponder()
 {
     return true;
index 7e1713d..3855ddd 100644 (file)
@@ -262,6 +262,7 @@ public:
 
     virtual void startWindowDrag() = 0;
     virtual NSWindow *platformWindow() = 0;
+    virtual void setShouldSuppressFirstResponderChanges(bool) = 0;
 
 #if WK_API_ENABLED
     virtual NSView *inspectorAttachmentView() = 0;
index 0f8e7e3..e2074cf 100644 (file)
@@ -154,7 +154,8 @@ private:
     void setPluginComplexTextInputState(uint64_t pluginComplexTextInputIdentifier, PluginComplexTextInputState) override;
 
     void makeFirstResponder() override;
-    
+    void setShouldSuppressFirstResponderChanges(bool shouldSuppress) override { m_shouldSuppressFirstResponderChanges = shouldSuppress; }
+
     void didPerformDictionaryLookup(const WebCore::DictionaryPopupInfo&) override;
     void dismissContentRelativeChildWindows(bool withAnimation = true) override;
 
@@ -242,6 +243,8 @@ private:
     std::unique_ptr<WebCore::AlternativeTextUIController> m_alternativeTextUIController;
 #endif
 
+    bool m_shouldSuppressFirstResponderChanges { false };
+
 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
     WebCore::WebMediaSessionManager& mediaSessionManager() override;
 #endif
index c5c4a65..819785b 100644 (file)
@@ -173,6 +173,9 @@ bool PageClientImpl::isViewFocused()
 
 void PageClientImpl::makeFirstResponder()
 {
+    if (m_shouldSuppressFirstResponderChanges)
+        return;
+
      [[m_view window] makeFirstResponder:m_view];
 }
     
index e23bda0..0d85b2b 100644 (file)
                C9BF06EF1E9C132500595E3E /* autoplay-muted-with-controls.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = C9BF06EE1E9C130400595E3E /* autoplay-muted-with-controls.html */; };
                C9C60E651E53A9DC006DA181 /* autoplay-check-frame.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = C9C60E631E53A9BA006DA181 /* autoplay-check-frame.html */; };
                C9C60E661E53A9DC006DA181 /* autoplay-check-in-iframe.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = C9C60E641E53A9BA006DA181 /* autoplay-check-in-iframe.html */; };
+               C9E6DD351EA97D0800DD78AA /* FirstResponderSuppression.mm in Sources */ = {isa = PBXBuildFile; fileRef = C9E6DD311EA972D800DD78AA /* FirstResponderSuppression.mm */; };
                CD321B041E3A85FA00EB21C8 /* video-with-muted-audio-and-webaudio.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = CD321B031E3A84B700EB21C8 /* video-with-muted-audio-and-webaudio.html */; };
                CD59F53419E9110D00CF1835 /* file-with-mse.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = CD59F53219E910AA00CF1835 /* file-with-mse.html */; };
                CD59F53519E9110D00CF1835 /* test-mse.mp4 in Copy Resources */ = {isa = PBXBuildFile; fileRef = CD59F53319E910BC00CF1835 /* test-mse.mp4 */; };
                C9BF06EE1E9C130400595E3E /* autoplay-muted-with-controls.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "autoplay-muted-with-controls.html"; sourceTree = "<group>"; };
                C9C60E631E53A9BA006DA181 /* autoplay-check-frame.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "autoplay-check-frame.html"; sourceTree = "<group>"; };
                C9C60E641E53A9BA006DA181 /* autoplay-check-in-iframe.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "autoplay-check-in-iframe.html"; sourceTree = "<group>"; };
+               C9E6DD311EA972D800DD78AA /* FirstResponderSuppression.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FirstResponderSuppression.mm; sourceTree = "<group>"; };
                CD225C071C45A69200140761 /* ParsedContentRange.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ParsedContentRange.cpp; sourceTree = "<group>"; };
                CD321B031E3A84B700EB21C8 /* video-with-muted-audio-and-webaudio.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "video-with-muted-audio-and-webaudio.html"; sourceTree = "<group>"; };
                CD5393C71757BA9700C07123 /* MD5.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MD5.cpp; sourceTree = "<group>"; };
                                1A9FB6CC1CA34BE500966124 /* EarlyKVOCrash.mm */,
                                4BB4160316815F9100824238 /* ElementAtPointInWebFrame.mm */,
                                9B79164F1BD89D0D00D50B8F /* FirstResponderScrollingPosition.mm */,
+                               C9E6DD311EA972D800DD78AA /* FirstResponderSuppression.mm */,
                                1A7E8B33181208DE00AEB74A /* FragmentNavigation.mm */,
                                CDBFCC431A9FF44800A7B691 /* FullscreenZoomInitialFrame.mm */,
                                9B4F8FA3159D52B1002D9F94 /* HTMLCollectionNamedItem.mm */,
                                7C1AF7951E8DCBAB002645B9 /* PrepareForMoveToWindow.mm in Sources */,
                                7CCE7F0B1A411AE600447C4C /* PreventEmptyUserAgent.cpp in Sources */,
                                7CCE7F2C1A411B1000447C4C /* PreventImageLoadWithAutoResizing.mm in Sources */,
+                               C9E6DD351EA97D0800DD78AA /* FirstResponderSuppression.mm in Sources */,
                                7CCE7F0C1A411AE600447C4C /* PrivateBrowsingPushStateNoHistoryCallback.cpp in Sources */,
                                7C83E0C11D0A652F00FEBCF3 /* ProvisionalURLNotChange.mm in Sources */,
                                7CCE7EC81A411A7E00447C4C /* PublicSuffix.mm in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/mac/FirstResponderSuppression.mm b/Tools/TestWebKitAPI/Tests/mac/FirstResponderSuppression.mm
new file mode 100644 (file)
index 0000000..d3f8946
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#import "config.h"
+
+#if PLATFORM(MAC) && WK_API_ENABLED
+
+#import "PlatformUtilities.h"
+#import <WebKit/WKWebViewPrivate.h>
+#import <wtf/RetainPtr.h>
+
+static bool finishedLoad = false;
+
+@interface FirstResponderNavigationDelegate : NSObject <WKNavigationDelegate>
+@end
+
+@implementation FirstResponderNavigationDelegate
+- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
+{
+    finishedLoad = true;
+}
+@end
+
+namespace TestWebKitAPI {
+
+TEST(WebKit2, FirstResponderSuppression)
+{
+    RetainPtr<NSWindow> window = adoptNS([[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600) styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]);
+    [window makeKeyAndOrderFront:nil];
+
+    RetainPtr<FirstResponderNavigationDelegate> delegate = adoptNS([[FirstResponderNavigationDelegate alloc] init]);
+
+    RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
+    [webView _setShouldSuppressFirstResponderChanges:YES];
+    [webView setNavigationDelegate:delegate.get()];
+    [[window contentView] addSubview:webView.get()];
+
+    // Start off with the content view as first responder.
+    [window makeFirstResponder:[window contentView]];
+    EXPECT_EQ([window firstResponder], [window contentView]);
+
+    // Ensure having an autofocused input field does not steal focus.
+    NSString *testHTML = @"<!doctype html><html><body><input type=\"text\" autofocus /></body></html>";
+    [webView loadHTMLString:testHTML baseURL:nil];
+    TestWebKitAPI::Util::run(&finishedLoad);
+    EXPECT_EQ([window firstResponder], [window contentView]);
+    finishedLoad = false;
+
+    [webView _setShouldSuppressFirstResponderChanges:NO];
+
+    // Ensure having an autofocused input field does steal focus.
+    [webView loadHTMLString:testHTML baseURL:nil];
+    TestWebKitAPI::Util::run(&finishedLoad);
+    EXPECT_NE([window firstResponder], [window contentView]);
+}
+
+} // namespace TestWebKitAPI
+
+#endif // PLATFORM(MAC) && WK_API_ENABLED