Skip to content

Commit

Permalink
Add a mechanism for tracking whether web pages have "recently" used g…
Browse files Browse the repository at this point in the history
…amepads

rdar://127892698
https://bugs.webkit.org/show_bug.cgi?id=274134

Reviewed by Sihui Liu.

In some configurations the UIProcess might want to configure specific behaviors based on
whether or not content in a web page has "recently" accessed gamepads via JavaScript.

Do to the polling nature of navigator.getGamepads() we consider a web page to have "recently"
accessed them as long as that function is called periodically during an arbitrary timespan
I have arbitrarily chosen as 1.5 seconds.

This patch adds the behavior and tests it via TestWebKitAPI.
Actually using this behavior to do something interesting will come in a followup.

* Source/WebCore/Headers.cmake:

* Source/WebCore/Modules/gamepad/NavigatorGamepad.cpp:
(WebCore::NavigatorGamepad::setGamepadsRecentlyAccessedThreshold):
(WebCore::NavigatorGamepad::maybeNotifyRecentAccess):
(WebCore::NavigatorGamepad::gamepads):
* Source/WebCore/Modules/gamepad/NavigatorGamepad.h:

* Source/WebCore/WebCore.xcodeproj/project.pbxproj:

* Source/WebCore/page/ChromeClient.h:
(WebCore::ChromeClient::gamepadsRecentlyAccessed):

* Source/WebKit/UIProcess/API/APIUIClient.h:
(API::UIClient::recentlyAccessedGamepadsForTesting):
(API::UIClient::stoppedAccessingGamepadsForTesting):

* Source/WebKit/UIProcess/API/Cocoa/WKUIDelegatePrivate.h:

* Source/WebKit/UIProcess/Cocoa/UIDelegate.h:
* Source/WebKit/UIProcess/Cocoa/UIDelegate.mm:
(WebKit::UIDelegate::setDelegate):
(WebKit::UIDelegate::UIClient::recentlyAccessedGamepadsForTesting):
(WebKit::UIDelegate::UIClient::stoppedAccessingGamepadsForTesting):

* Source/WebKit/UIProcess/WebPageProxy.cpp:
(WebKit::m_recentGamepadAccessHysteresis):
(WebKit::WebPageProxy::recentGamepadAccessStateChanged):
(WebKit::WebPageProxy::gamepadsRecentlyAccessed):
(WebKit::m_browsingContextGroup): Deleted.
* Source/WebKit/UIProcess/WebPageProxy.h:
* Source/WebKit/UIProcess/WebPageProxy.messages.in:

* Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp:
(WebKit::WebChromeClient::gamepadsRecentlyAccessed):
* Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h:

* Source/WebKit/WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::gamepadsRecentlyAccessed):
* Source/WebKit/WebProcess/WebPage/WebPage.h:

* Source/WebKit/WebProcess/WebProcess.cpp:
(WebKit::WebProcess::initializeWebProcess):

* Tools/TestWebKitAPI/Tests/mac/HIDGamepads.mm:
(-[GamepadUIDelegate _webViewRecentlyAccessedGamepadsForTesting:]):
(-[GamepadUIDelegate _webViewStoppedAccessingGamepadsForTesting:]):
(TestWebKitAPI::(Gamepad, GamepadState)):

Canonical link: https://commits.webkit.org/278782@main
  • Loading branch information
beidson committed May 14, 2024
1 parent 382ae7f commit b5e71b3
Show file tree
Hide file tree
Showing 20 changed files with 220 additions and 2 deletions.
1 change: 1 addition & 0 deletions Source/WebCore/Headers.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS

Modules/gamepad/GamepadEffectParameters.h
Modules/gamepad/GamepadHapticEffectType.h
Modules/gamepad/NavigatorGamepad.h

Modules/geolocation/Geolocation.h
Modules/geolocation/GeolocationClient.h
Expand Down
36 changes: 36 additions & 0 deletions Source/WebCore/Modules/gamepad/NavigatorGamepad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@

#if ENABLE(GAMEPAD)

#include "Chrome.h"
#include "ChromeClient.h"
#include "Document.h"
#include "Gamepad.h"
#include "GamepadManager.h"
#include "GamepadProvider.h"
#include "LocalDOMWindow.h"
#include "Navigator.h"
#include "Page.h"
#include "PermissionsPolicy.h"
#include "PlatformGamepad.h"

Expand Down Expand Up @@ -89,8 +92,41 @@ ExceptionOr<const Vector<RefPtr<Gamepad>>&> NavigatorGamepad::getGamepads(Naviga
return NavigatorGamepad::from(navigator)->gamepads();
}

// The UIProcess tracks when a WebPage has recently used gamepads to configure certain behaviors on the page.
//
// Once a WebPage notifies the UIProcess that gamepads have been accessed, the UIProcess starts a timer with
// a short delay, after which it assumes that WebPage is no longer actively using gamepads.
//
// This works because of the polling nature of the gamepad API - If a web page is using gamepads it will be
// accessing them multiple times per second.
//
// WebPages need to continuously tell the UIProcess that gamepads have been used recently, but can do so lazily;
// As long as a WebPage continuously using gamepads notifies the UIProcess at least once during the UIProcess's
// timer delay, things will work as expected.
//
// So it follows: Web processes have this value initialized to be compatible with the UIProcess timer threshold.

static Seconds s_gamepadsRecentlyAccessedThreshold;

void NavigatorGamepad::setGamepadsRecentlyAccessedThreshold(Seconds threshold)
{
// This value should only initialized once.
RELEASE_ASSERT(!s_gamepadsRecentlyAccessedThreshold);
s_gamepadsRecentlyAccessedThreshold = threshold;
}

Seconds NavigatorGamepad::gamepadsRecentlyAccessedThreshold()
{
return s_gamepadsRecentlyAccessedThreshold;
}

const Vector<RefPtr<Gamepad>>& NavigatorGamepad::gamepads()
{
if (RefPtr frame = m_navigator.frame()) {
if (RefPtr page = frame->protectedPage())
page->gamepadsRecentlyAccessed();
}

if (m_gamepads.isEmpty())
return m_gamepads;

Expand Down
5 changes: 5 additions & 0 deletions Source/WebCore/Modules/gamepad/NavigatorGamepad.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#if ENABLE(GAMEPAD)

#include "Supplementable.h"
#include <wtf/MonotonicTime.h>
#include <wtf/Vector.h>
#include <wtf/WeakPtr.h>

Expand Down Expand Up @@ -64,10 +65,14 @@ class NavigatorGamepad : public Supplement<Navigator>, public CanMakeWeakPtr<Nav

Ref<Gamepad> gamepadFromPlatformGamepad(PlatformGamepad&);

WEBCORE_EXPORT static void setGamepadsRecentlyAccessedThreshold(Seconds);
static Seconds gamepadsRecentlyAccessedThreshold();

private:
static ASCIILiteral supplementName();

void gamepadsBecameVisible();
void maybeNotifyRecentAccess();

const Vector<RefPtr<Gamepad>>& gamepads();

Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/WebCore.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1947,6 +1947,7 @@
5130F2F624AEA60A00E1D0A0 /* GameControllerSoftLink.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5130F2F424AEA60300E1D0A0 /* GameControllerSoftLink.mm */; };
51327D6011A33A2B004F9D65 /* SinkDocument.h in Headers */ = {isa = PBXBuildFile; fileRef = 51327D5E11A33A2B004F9D65 /* SinkDocument.h */; };
513CEA592B34ACE5000FC060 /* PredefinedColorSpace.h in Headers */ = {isa = PBXBuildFile; fileRef = BCA5591A263F10B9007F19B3 /* PredefinedColorSpace.h */; settings = {ATTRIBUTES = (Private, ); }; };
513EB5F22BF2D9B800BC98B1 /* NavigatorGamepad.h in Headers */ = {isa = PBXBuildFile; fileRef = 51A4BB0E1954D62700FA5C2E /* NavigatorGamepad.h */; settings = {ATTRIBUTES = (Private, ); }; };
513F14540AB634C400094DDF /* IconLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 513F14520AB634C400094DDF /* IconLoader.h */; };
51405C89190B014400754F94 /* SelectionGeometryGatherer.h in Headers */ = {isa = PBXBuildFile; fileRef = 51405C87190B014400754F94 /* SelectionGeometryGatherer.h */; settings = {ATTRIBUTES = (Private, ); }; };
514129991C6976900059E714 /* IDBRequestCompletionEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 514129971C6976150059E714 /* IDBRequestCompletionEvent.h */; };
Expand Down Expand Up @@ -41301,6 +41302,7 @@
E596DD29251903D200C275A7 /* NavigatorContacts.h in Headers */,
F4034FAE275EAD76003A81F8 /* NavigatorCookieConsent.h in Headers */,
372D3E57216578AE00C5E021 /* NavigatorCredentials.h in Headers */,
513EB5F22BF2D9B800BC98B1 /* NavigatorGamepad.h in Headers */,
9711460414EF009A00674FD9 /* NavigatorGeolocation.h in Headers */,
6B507A24234BF34100BE7C62 /* NavigatorIsLoggedIn.h in Headers */,
5EA725D61ACABD5700EAD17B /* NavigatorMediaDevices.h in Headers */,
Expand Down
4 changes: 4 additions & 0 deletions Source/WebCore/page/ChromeClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,10 @@ class ChromeClient {

virtual void didAdjustVisibilityWithSelectors(Vector<String>&&) { }

#if ENABLE(GAMEPAD)
virtual void gamepadsRecentlyAccessed() { }
#endif

WEBCORE_EXPORT virtual ~ChromeClient();

protected:
Expand Down
12 changes: 12 additions & 0 deletions Source/WebCore/page/Page.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
#include "ModelPlayerProvider.h"
#include "NavigationScheduler.h"
#include "Navigator.h"
#include "NavigatorGamepad.h"
#include "OpportunisticTaskScheduler.h"
#include "PageColorSampler.h"
#include "PageConfiguration.h"
Expand Down Expand Up @@ -4800,4 +4801,15 @@ void Page::setDefaultSpatialTrackingLabel(const String& label)
}
#endif

#if ENABLE(GAMEPAD)
void Page::gamepadsRecentlyAccessed()
{
if (MonotonicTime::now() - m_lastAccessNotificationTime < NavigatorGamepad::gamepadsRecentlyAccessedThreshold())
return;

chrome().client().gamepadsRecentlyAccessed();
m_lastAccessNotificationTime = MonotonicTime::now();
}
#endif

} // namespace WebCore
10 changes: 9 additions & 1 deletion Source/WebCore/page/Page.h
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,10 @@ class Page : public RefCounted<Page>, public Supplementable<Page>, public CanMak
const String& defaultSpatialTrackingLabel() const { return m_defaultSpatialTrackingLabel; }
#endif

#if ENABLE(GAMEPAD)
void gamepadsRecentlyAccessed();
#endif

private:
explicit Page(PageConfiguration&&);

Expand Down Expand Up @@ -1514,7 +1518,11 @@ class Page : public RefCounted<Page>, public Supplementable<Page>, public CanMak
#if HAVE(SPATIAL_TRACKING_LABEL)
String m_defaultSpatialTrackingLabel;
#endif
};

#if ENABLE(GAMEPAD)
MonotonicTime m_lastAccessNotificationTime;
#endif
}; // class Page

inline Page* Frame::page() const
{
Expand Down
5 changes: 5 additions & 0 deletions Source/WebKit/UIProcess/API/APIUIClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ class UIClient {
virtual void updateClientBadge(WebKit::WebPageProxy&, const WebCore::SecurityOriginData&, std::optional<uint64_t>) { }

virtual void didAdjustVisibilityWithSelectors(WebKit::WebPageProxy&, Vector<WTF::String>&&) { }

#if ENABLE(GAMEPAD)
virtual void recentlyAccessedGamepadsForTesting(WebKit::WebPageProxy&) { }
virtual void stoppedAccessingGamepadsForTesting(WebKit::WebPageProxy&) { }
#endif
};

} // namespace API
3 changes: 3 additions & 0 deletions Source/WebKit/UIProcess/API/Cocoa/WKUIDelegatePrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ struct UIEdgeInsets;

- (void)_webView:(WKWebView *)webView didAdjustVisibilityWithSelectors:(NSArray<NSString *> *)selectors WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA), visionos(WK_XROS_TBA));

- (void)_webViewRecentlyAccessedGamepadsForTesting:(WKWebView *)webView WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA), visionos(WK_XROS_TBA));
- (void)_webViewStoppedAccessingGamepadsForTesting:(WKWebView *)webView WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA), visionos(WK_XROS_TBA));

#if TARGET_OS_IPHONE

- (BOOL)_webView:(WKWebView *)webView shouldIncludeAppLinkActionsForElement:(_WKActivatedElementInfo *)element WK_API_AVAILABLE(ios(9.0));
Expand Down
10 changes: 10 additions & 0 deletions Source/WebKit/UIProcess/Cocoa/UIDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ class UIDelegate : public CanMakeWeakPtr<UIDelegate> {

void didAdjustVisibilityWithSelectors(WebPageProxy&, Vector<String>&&) final;

#if ENABLE(GAMEPAD)
void recentlyAccessedGamepadsForTesting(WebPageProxy&) final;
void stoppedAccessingGamepadsForTesting(WebPageProxy&) final;
#endif

WeakPtr<UIDelegate> m_uiDelegate;
};

Expand Down Expand Up @@ -312,6 +317,11 @@ class UIDelegate : public CanMakeWeakPtr<UIDelegate> {
bool webViewUpdatedAppBadge : 1;
bool webViewUpdatedClientBadge : 1;
bool webViewDidAdjustVisibilityWithSelectors : 1;

#if ENABLE(GAMEPAD)
bool webViewRecentlyAccessedGamepadsForTesting : 1;
bool webViewStoppedAccessingGamepadsForTesting : 1;
#endif
} m_delegateMethods;
};

Expand Down
37 changes: 37 additions & 0 deletions Source/WebKit/UIProcess/Cocoa/UIDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,11 @@
m_delegateMethods.webViewUpdatedClientBadge = [delegate respondsToSelector:@selector(_webView:updatedClientBadge:fromSecurityOrigin:)];

m_delegateMethods.webViewDidAdjustVisibilityWithSelectors = [delegate respondsToSelector:@selector(_webView:didAdjustVisibilityWithSelectors:)];

#if ENABLE(GAMEPAD)
m_delegateMethods.webViewRecentlyAccessedGamepadsForTesting = [delegate respondsToSelector:@selector(_webViewRecentlyAccessedGamepadsForTesting:)];
m_delegateMethods.webViewStoppedAccessingGamepadsForTesting = [delegate respondsToSelector:@selector(_webViewStoppedAccessingGamepadsForTesting:)];
#endif
}

#if ENABLE(CONTEXT_MENUS)
Expand Down Expand Up @@ -1937,6 +1942,38 @@ static WebAuthenticationPanelResult webAuthenticationPanelResult(_WKWebAuthentic
[delegate _webView:m_uiDelegate->m_webView.get().get() didAdjustVisibilityWithSelectors:nsSelectors.get()];
}

#if ENABLE(GAMEPAD)
void UIDelegate::UIClient::recentlyAccessedGamepadsForTesting(WebPageProxy&)
{
if (!m_uiDelegate)
return;

if (!m_uiDelegate->m_delegateMethods.webViewRecentlyAccessedGamepadsForTesting)
return;

auto delegate = (id<WKUIDelegatePrivate>)m_uiDelegate->m_delegate.get();
if (!delegate)
return;

[delegate _webViewRecentlyAccessedGamepadsForTesting:m_uiDelegate->m_webView.get().get()];
}

void UIDelegate::UIClient::stoppedAccessingGamepadsForTesting(WebPageProxy&)
{
if (!m_uiDelegate)
return;

if (!m_uiDelegate->m_delegateMethods.webViewStoppedAccessingGamepadsForTesting)
return;

auto delegate = (id<WKUIDelegatePrivate>)m_uiDelegate->m_delegate.get();
if (!delegate)
return;

[delegate _webViewStoppedAccessingGamepadsForTesting:m_uiDelegate->m_webView.get().get()];
}
#endif

#if ENABLE(WEBXR)
static _WKXRSessionMode toWKXRSessionMode(PlatformXR::SessionMode mode)
{
Expand Down
27 changes: 26 additions & 1 deletion Source/WebKit/UIProcess/WebPageProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,9 @@ WebPageProxy::WebPageProxy(PageClient& pageClient, WebProcessProxy& process, Ref
, m_limitsNavigationsToAppBoundDomains(m_configuration->limitsNavigationsToAppBoundDomains())
#endif
, m_browsingContextGroup(m_configuration->browsingContextGroup())
#if ENABLE(GAMEPAD)
, m_recentGamepadAccessHysteresis([this](PAL::HysteresisState state) { recentGamepadAccessStateChanged(state); }, gamepadsRecentlyAccessedThreshold)
#endif
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "constructor:");

Expand Down Expand Up @@ -10455,7 +10458,29 @@ void WebPageProxy::gamepadActivity(const Vector<std::optional<GamepadData>>& gam
send(Messages::WebPage::GamepadActivity(gamepadDatas, eventVisibility));
}

#endif
void WebPageProxy::recentGamepadAccessStateChanged(PAL::HysteresisState state)
{
switch (state) {
case PAL::HysteresisState::Started:
m_uiClient->recentlyAccessedGamepadsForTesting(*this);
break;
case PAL::HysteresisState::Stopped:
m_uiClient->stoppedAccessingGamepadsForTesting(*this);
}
}

void WebPageProxy::gamepadsRecentlyAccessed()
{
// FIXME: We'd like to message_check here to validate the process should be allowed
// to refresh the "recently using gamepads" state.
// We could check our "set of processes using gamepads" but it is already driven
// by web process messages, therefore a compromised WebProcess can add itself.
// Is there something meaningful we can do here?

m_recentGamepadAccessHysteresis.impulse();
}

#endif // ENABLE(GAMEPAD)

void WebPageProxy::didReceiveAuthenticationChallengeProxy(Ref<AuthenticationChallengeProxy>&& authenticationChallenge, NegotiatedLegacyTLS negotiatedLegacyTLS)
{
Expand Down
12 changes: 12 additions & 0 deletions Source/WebKit/UIProcess/WebPageProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "MessageSender.h"
#include <WebCore/FrameIdentifier.h>
#include <WebCore/NowPlayingMetadataObserver.h>
#include <pal/HysteresisActivity.h>
#include <wtf/CheckedRef.h>
#include <wtf/CompletionHandler.h>
#include <wtf/OptionSet.h>
Expand Down Expand Up @@ -1897,7 +1898,10 @@ class WebPageProxy final : public API::ObjectImpl<API::Object::Type::Page>, publ
#endif

#if ENABLE(GAMEPAD)
static constexpr Seconds gamepadsRecentlyAccessedThreshold { 1500_ms };

void gamepadActivity(const Vector<std::optional<GamepadData>>&, WebCore::EventMakesGamepadsVisible);
void gamepadsRecentlyAccessed();
#endif

void isLoadingChanged();
Expand Down Expand Up @@ -3049,6 +3053,10 @@ class WebPageProxy final : public API::ObjectImpl<API::Object::Type::Page>, publ

void frameNameChanged(IPC::Connection&, WebCore::FrameIdentifier, const String& frameName);

#if ENABLE(GAMEPAD)
void recentGamepadAccessStateChanged(PAL::HysteresisState);
#endif

struct Internals;
Internals& internals() { return m_internals; }
const Internals& internals() const { return m_internals; }
Expand Down Expand Up @@ -3582,6 +3590,10 @@ class WebPageProxy final : public API::ObjectImpl<API::Object::Type::Page>, publ
#if ENABLE(UNIFIED_TEXT_REPLACEMENT)
bool m_isUnifiedTextReplacementActive { false };
#endif

#if ENABLE(GAMEPAD)
PAL::HysteresisActivity m_recentGamepadAccessHysteresis;
#endif
};

} // namespace WebKit
4 changes: 4 additions & 0 deletions Source/WebKit/UIProcess/WebPageProxy.messages.in
Original file line number Diff line number Diff line change
Expand Up @@ -646,4 +646,8 @@ messages -> WebPageProxy {
DidAdjustVisibilityWithSelectors(Vector<String> selectors)

FrameNameChanged(WebCore::FrameIdentifier frameID, String frameName)

#if ENABLE(GAMEPAD)
GamepadsRecentlyAccessed()
#endif
}
7 changes: 7 additions & 0 deletions Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1823,4 +1823,11 @@ void WebChromeClient::didAdjustVisibilityWithSelectors(Vector<String>&& selector
return protectedPage()->didAdjustVisibilityWithSelectors(WTFMove(selectors));
}

#if ENABLE(GAMEPAD)
void WebChromeClient::gamepadsRecentlyAccessed()
{
protectedPage()->gamepadsRecentlyAccessed();
}
#endif

} // namespace WebKit
4 changes: 4 additions & 0 deletions Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,10 @@ class WebChromeClient final : public WebCore::ChromeClient {

void didAdjustVisibilityWithSelectors(Vector<String>&&) final;

#if ENABLE(GAMEPAD)
void gamepadsRecentlyAccessed() final;
#endif

mutable bool m_cachedMainFrameHasHorizontalScrollbar { false };
mutable bool m_cachedMainFrameHasVerticalScrollbar { false };

Expand Down
5 changes: 5 additions & 0 deletions Source/WebKit/WebProcess/WebPage/WebPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8102,6 +8102,11 @@ void WebPage::gamepadActivity(const Vector<std::optional<GamepadData>>& gamepadD
WebGamepadProvider::singleton().gamepadActivity(gamepadDatas, eventVisibilty);
}

void WebPage::gamepadsRecentlyAccessed()
{
send(Messages::WebPageProxy::GamepadsRecentlyAccessed());
}

#endif

#if ENABLE(POINTER_LOCK)
Expand Down
1 change: 1 addition & 0 deletions Source/WebKit/WebProcess/WebPage/WebPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -1396,6 +1396,7 @@ class WebPage : public API::ObjectImpl<API::Object::Type::BundlePage>, public IP

#if ENABLE(GAMEPAD)
void gamepadActivity(const Vector<std::optional<GamepadData>>&, WebCore::EventMakesGamepadsVisible);
void gamepadsRecentlyAccessed();
#endif

#if ENABLE(POINTER_LOCK)
Expand Down
Loading

0 comments on commit b5e71b3

Please sign in to comment.