Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Notification::permission to follow the same code path as Permissions::query #2982

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

RupinMittal
Copy link
Contributor

@RupinMittal RupinMittal commented Aug 3, 2022

92a17b9

Refactor Notification::permission to follow the same code path as Permissions::query
https://bugs.webkit.org/show_bug.cgi?id=243502

Reviewed by NOBODY (OOPS!).

There are two functions for obtaining the permission state of the Notifications API.
We need to provide support for both to ensure compatibility with web standards. This
patch ensures that they follow the same code path since having two different paths is
unnecessary and hurts maintainability. This code path goes to the UIProcess and then
to the client to get the permission state.

This patch also removes the now uneeded cache in WebNotificationManager that
Notification::permission was relying on and enables the WebProcess cache in
WebPermissionController to be used.

* LayoutTests/http/tests/notifications/service-worker-notification-permission-expected.txt: Added.
* LayoutTests/http/tests/notifications/service-worker-notification-permission.html: Added.
* LayoutTests/http/tests/notifications/service-worker-notification-permission.js: Added.
(self.onmessage):
* LayoutTests/http/tests/push-api/subscribe-deny-permissions-on-prompt-expected.txt:
* LayoutTests/http/tests/push-api/subscribe-deny-permissions-on-prompt.html:
* LayoutTests/platform/mac-wk1/TestExpectations:
* Source/WebCore/Headers.cmake:
* Source/WebCore/Modules/permissions/PermissionController.h:
* Source/WebCore/Modules/permissions/Permissions.cpp:
(WebCore::Permissions::sourceFromContext):
(WebCore::sourceFromContext): Deleted.
* Source/WebCore/Modules/permissions/Permissions.h:
* Source/WebCore/WebCore.xcodeproj/project.pbxproj:
* Source/WebKit/UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::queryPermission):
(WebKit::WebPageProxy::clearNotificationPermissionState):
* Source/WebKit/UIProcess/WebPermissionControllerProxy.cpp:
(WebKit::WebPermissionControllerProxy::querySync):
* Source/WebKit/UIProcess/WebPermissionControllerProxy.h:
* Source/WebKit/UIProcess/WebPermissionControllerProxy.messages.in:
* Source/WebKit/WebProcess/Notifications/NotificationPermissionRequestManager.cpp:
(WebKit::NotificationPermissionRequestManager::startRequest):
(WebKit::NotificationPermissionRequestManager::permissionLevel): Deleted.
* Source/WebKit/WebProcess/Notifications/NotificationPermissionRequestManager.h:
* Source/WebKit/WebProcess/Notifications/WebNotificationManager.cpp:
(WebKit::WebNotificationManager::initialize):
(WebKit::WebNotificationManager::didUpdateNotificationDecision):
(WebKit::WebNotificationManager::didRemoveNotificationDecisions):
(WebKit::WebNotificationManager::removeAllPermissionsForTesting):
(WebKit::WebNotificationManager::policyForOrigin const): Deleted.
* Source/WebKit/WebProcess/Notifications/WebNotificationManager.h:
* Source/WebKit/WebProcess/WebCoreSupport/WebNotificationClient.cpp:
(WebKit::WebNotificationClient::requestPermission):
(WebKit::WebNotificationClient::checkPermission):
(WebKit::WebNotificationClient::clearNotificationPermissionState): Deleted.
* Source/WebKit/WebProcess/WebCoreSupport/WebNotificationClient.h:
* Source/WebKit/WebProcess/WebCoreSupport/WebPermissionController.cpp:
(WebKit::WebPermissionController::query):
(WebKit::WebPermissionController::querySync):
(WebKit::WebPermissionController::webPageProxyIdentifier):
(WebKit::WebPermissionController::removeEntryFromCache):
(WebKit::WebPermissionController::clearCache):
(WebKit::WebPermissionController::tryProcessingRequests):
* Source/WebKit/WebProcess/WebCoreSupport/WebPermissionController.h:
(isType):
* Source/WebKit/WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::clearNotificationPermissionState): Deleted.
* Source/WebKit/WebProcess/WebPage/WebPage.h:
* Source/WebKit/WebProcess/WebPage/WebPage.messages.in:
* Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp:
(WTR::TestRunner::removeAllWebNotificationPermissions):
* Tools/WebKitTestRunner/TestController.cpp:
(WTR::TestController::removeAllWebNotificationPermissions):
* Tools/WebKitTestRunner/TestController.h:
* Tools/WebKitTestRunner/TestInvocation.cpp:
(WTR::TestInvocation::didReceiveSynchronousMessageFromInjectedBundle):

92a17b9

Misc iOS, tvOS & watchOS macOS Linux Windows
βœ… πŸ§ͺ style βœ… πŸ›  ios βœ… πŸ›  mac βœ… πŸ›  wpe βœ… πŸ›  πŸ§ͺ win
βœ… πŸ§ͺ bindings βœ… πŸ›  ios-sim βœ… πŸ›  mac-debug βœ… πŸ›  gtk βœ… πŸ›  wincairo
βœ… πŸ§ͺ webkitperl βœ… πŸ§ͺ ios-wk2 βœ… πŸ›  mac-AS-debug βœ… πŸ§ͺ gtk-wk2
βœ… πŸ§ͺ api-ios βœ… πŸ§ͺ api-mac ❌ πŸ§ͺ api-gtk
βœ… πŸ›  tv βœ… πŸ§ͺ mac-wk1
βœ… πŸ›  tv-sim βœ… πŸ§ͺ mac-wk2
βœ… πŸ›  watch βœ… πŸ§ͺ mac-AS-debug-wk2
βœ… πŸ›  watch-sim βœ… πŸ§ͺ mac-wk2-stress

@RupinMittal RupinMittal self-assigned this Aug 3, 2022
@RupinMittal RupinMittal added New Bugs Unclassified bugs are placed in this component until the correct component can be determined. WebKit Nightly Build labels Aug 3, 2022
Source/WebCore/Modules/permissions/PermissionController.h Outdated Show resolved Hide resolved
Source/WebCore/Modules/permissions/PermissionController.h Outdated Show resolved Hide resolved
Source/WebKit/UIProcess/WebPageProxy.messages.in Outdated Show resolved Hide resolved
Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp Outdated Show resolved Hide resolved
Tools/WebKitTestRunner/TestController.cpp Outdated Show resolved Hide resolved
Copy link
Contributor

@szewai szewai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to be be done after adding Worker support for Permissions API, and is better be done after change event is supported (or making web process able to cache the state to avoid sync messages).

@webkit-early-warning-system webkit-early-warning-system added the merging-blocked Applied to prevent a change from being merged label Aug 4, 2022
@RupinMittal RupinMittal closed this Aug 4, 2022
@RupinMittal RupinMittal reopened this Aug 25, 2022
@RupinMittal RupinMittal removed the merging-blocked Applied to prevent a change from being merged label Aug 25, 2022
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Aug 25, 2022
Copy link
Contributor

@szewai szewai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test failure looks relevant, please fix them

@RupinMittal RupinMittal removed the merging-blocked Applied to prevent a change from being merged label Aug 26, 2022
…missions::query

https://bugs.webkit.org/show_bug.cgi?id=243502

Reviewed by NOBODY (OOPS!).

There are two functions for obtaining the permission state of the Notifications API.
We need to provide support for both to ensure compatibility with web standards. This
patch ensures that they follow the same code path since having two different paths is
unnecessary and hurts maintainability. This code path goes to the UIProcess and then
to the client to get the permission state.

This patch also removes the now uneeded cache in WebNotificationManager that
Notification::permission was relying on and enables the WebProcess cache in
WebPermissionController to be used.

* LayoutTests/http/tests/notifications/service-worker-notification-permission-expected.txt: Added.
* LayoutTests/http/tests/notifications/service-worker-notification-permission.html: Added.
* LayoutTests/http/tests/notifications/service-worker-notification-permission.js: Added.
(self.onmessage):
* LayoutTests/http/tests/push-api/subscribe-deny-permissions-on-prompt-expected.txt:
* LayoutTests/http/tests/push-api/subscribe-deny-permissions-on-prompt.html:
* LayoutTests/platform/mac-wk1/TestExpectations:
* Source/WebCore/Headers.cmake:
* Source/WebCore/Modules/permissions/PermissionController.h:
* Source/WebCore/Modules/permissions/Permissions.cpp:
(WebCore::Permissions::sourceFromContext):
(WebCore::sourceFromContext): Deleted.
* Source/WebCore/Modules/permissions/Permissions.h:
* Source/WebCore/WebCore.xcodeproj/project.pbxproj:
* Source/WebKit/UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::queryPermission):
(WebKit::WebPageProxy::clearNotificationPermissionState):
* Source/WebKit/UIProcess/WebPermissionControllerProxy.cpp:
(WebKit::WebPermissionControllerProxy::querySync):
* Source/WebKit/UIProcess/WebPermissionControllerProxy.h:
* Source/WebKit/UIProcess/WebPermissionControllerProxy.messages.in:
* Source/WebKit/WebProcess/Notifications/NotificationPermissionRequestManager.cpp:
(WebKit::NotificationPermissionRequestManager::startRequest):
(WebKit::NotificationPermissionRequestManager::permissionLevel): Deleted.
* Source/WebKit/WebProcess/Notifications/NotificationPermissionRequestManager.h:
* Source/WebKit/WebProcess/Notifications/WebNotificationManager.cpp:
(WebKit::WebNotificationManager::initialize):
(WebKit::WebNotificationManager::didUpdateNotificationDecision):
(WebKit::WebNotificationManager::didRemoveNotificationDecisions):
(WebKit::WebNotificationManager::removeAllPermissionsForTesting):
(WebKit::WebNotificationManager::policyForOrigin const): Deleted.
* Source/WebKit/WebProcess/Notifications/WebNotificationManager.h:
* Source/WebKit/WebProcess/WebCoreSupport/WebNotificationClient.cpp:
(WebKit::WebNotificationClient::requestPermission):
(WebKit::WebNotificationClient::checkPermission):
(WebKit::WebNotificationClient::clearNotificationPermissionState): Deleted.
* Source/WebKit/WebProcess/WebCoreSupport/WebNotificationClient.h:
* Source/WebKit/WebProcess/WebCoreSupport/WebPermissionController.cpp:
(WebKit::WebPermissionController::query):
(WebKit::WebPermissionController::querySync):
(WebKit::WebPermissionController::webPageProxyIdentifier):
(WebKit::WebPermissionController::removeEntryFromCache):
(WebKit::WebPermissionController::clearCache):
(WebKit::WebPermissionController::tryProcessingRequests):
* Source/WebKit/WebProcess/WebCoreSupport/WebPermissionController.h:
(isType):
* Source/WebKit/WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::clearNotificationPermissionState): Deleted.
* Source/WebKit/WebProcess/WebPage/WebPage.h:
* Source/WebKit/WebProcess/WebPage/WebPage.messages.in:
* Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp:
(WTR::TestRunner::removeAllWebNotificationPermissions):
* Tools/WebKitTestRunner/TestController.cpp:
(WTR::TestController::removeAllWebNotificationPermissions):
* Tools/WebKitTestRunner/TestController.h:
* Tools/WebKitTestRunner/TestInvocation.cpp:
(WTR::TestInvocation::didReceiveSynchronousMessageFromInjectedBundle):
reject('Test requires internals.');
return;
}
if (!window.internals)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain in commit message that why this change is needed? It's not clear to me why pushManager would return different results after this patch

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WebNotificationClient::checkPermission (and thus, Notification::permission) used to get the permission state from a cache in WebNotificationManager. This cache did not get updated by calling TestController::denyNotificationPermissionOnPrompt and then WebNotificationClient::requestPermission (which is what happens in this test). So when request2 occurred, WebNotificationClient::checkPermission would return default, then fail the transient activation check and reject the promise with a "user gesture" error.

This patch removes this cache and refactors WebNotificationClient::checkPermission (and thus, Notification::permission) to get the permission state from the client (which is TestController in the case of this test). The cache in TestController that stores the permissions state does get updated by calling TestController::denyNotificationPermissionOnPrompt and then WebNotificationClient::requestPermission. So now, when request2 occurs, WebNotificationClient::checkPermission returns denied and rejects the promise with a "user denied push permission" error before reaching the transient activation check.

I spoke with @bnham about this and learned that the point of this layout test is to check that given a user gesture, the first call to PushManager::subscribe would consume the transient activation and that the second call would not have a transient activation. Due to the change described above, this check on the second call was being circumvented. We can ensure that this check occurs by altering the test the way I did in this patch.

You're right, the reason for change is not clear just by looking at the test, so I'll update the commit message with the above explanation once the bots finish and I make any other suggested changes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what's described above, It seems the right thing is to update WebKitTestRunner, making it cache the request result like a real client would do, instead of changing the test, since you don't intend to change the tested behavior (that first subscribe request fails due to user activation and second fails due to permission request).

Our current design for permissions is that each query and request goes to client, which means client is responsible to cache the result if needed.
Specifically, you might call setPermission on WebNotificationProvider in TestController::decidePolicyForNotificationPermissionRequest. (to simulate user denies the prompt and browser remembers the choice).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe WebKitTestRunner already caches the result. TestController::decidePolicyForNotificationPermissionRequest. already calls setPermission on WebNotificationProvider. denyOnPrompt means that if you request/prompt for permission, the state will be set to denied. The test was expecting that after denyOnPrompt is called, then first request does a prompt, the second request will get that the permission state is default? But that doesn't seem to make sense. The permission state should be denied, no? The reason that the test was passing before this patch was because it was relying on the fact that WebKitTestRunner wasn't updating the cache in WebNotificationManager. Now that this cache is gone, and checkPermission actually goes to the client, and testRunner returns the result based on its cache in WebNotificationProvider, the second request correctly gets back that the permission state is denied.

Comment on lines +83 to +88
auto& webPermissionController = downcast<WebPermissionController>(PermissionController::shared());
for (auto const& [originString, allowed] : parameters.notificationPermissions) {
auto topOrigin = SecurityOrigin::createFromString(originString)->data();
auto permissionState = allowed ? PermissionState::Granted : PermissionState::Denied;
webPermissionController.updateCache(ClientOrigin { topOrigin, SecurityOriginData { } }, PermissionDescriptor { PermissionName::Notifications }, permissionState);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change? I thought we only want one implementation for both APIs, i.e. we will only use queryPermission to pull notification's permission.
This makes the cache dependent on WKNotificationManager: e.g. if client implements queryPermission but not WKNotificationManager, the result will be wrong (because cache will not be updated).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This patch contains other code changes that ensure that calling queryPermission will also update the cache (if the permission state being queried is the state of the Notifications API.

The reason for this change was that in a previous version of this patch, the EWS bots detected a API test failure on GTK. The failure was that Notification::permission was incorrectly returning "default" instead of "granted". It turns out that the test client did not implement queryPermission and the test had been succeeding because of the cache in WebNotificationManager. Since this patch removes that cache and refactors Notification::permission to use queryPermission, the result was coming back as default. There were two options to fix this:

  1. Implement queryPermission in the GTK test client
  2. Make the not-working WebProcess cache in WebPermissionController work for notifications.

The second option seemed easier and more beneficial since we'll have to do this for the on change event later on anyways. I wasn't sure how to test if this will fix the API test since it was a GTK failure, so I guess we'll have to wait for the bots to check. :(

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This patch contains other code changes that ensure that calling queryPermission will also update the cache (if the permission state being queried is the state of the Notifications API.

Let's say client implements queryPermission but not WKNotificationManager, and user sets allowed notification (e.g. in per-site settings in Safari) for a site.
For the first permission::query, client returns allowed, and WebPermissionController caches the result. Then user decides to change the setting to denied (no longer wants to receive notification from the site), and tells WebKit client. For the second permission query, what would be returned with your patch?

IIUC, before this patch, notification.permission relies on WKNotificationManager and permission.query relies on queryPermission. After this patch, both rely on either WKNotificationManager or queryPermission + WKNotificationManager, which does not seem like a progress to me.

The second option seemed easier and more beneficial since we'll have to do this for the on change event later on anyways.

The reason we implement this before change event is because we think this change would work on its own. If this change relies on cache to work, then I think we should implement change event first, since the cache might not be in this form (an origin-to-permission map). You can imagine having WebPermissionController track PermissionStatus (or PermissionObserver) objects which hold the state, then you will need to revamp the cache.

for (auto const& [originString, allowed] : parameters.notificationPermissions) {
auto topOrigin = SecurityOrigin::createFromString(originString)->data();
auto permissionState = allowed ? PermissionState::Granted : PermissionState::Denied;
webPermissionController.updateCache(ClientOrigin { topOrigin, SecurityOriginData { } }, PermissionDescriptor { PermissionName::Notifications }, permissionState);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment, your ClientOrigin seems wrong.

auto& webPermissionController = downcast<WebPermissionController>(PermissionController::shared());
auto topOrigin = SecurityOrigin::createFromString(originString)->data();
auto permissionState = allowed ? PermissionState::Granted : PermissionState::Denied;
webPermissionController.updateCache(ClientOrigin { topOrigin, SecurityOriginData { } }, PermissionDescriptor { PermissionName::Notifications }, permissionState);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, bad ClientOrigin.

for (auto& originString : originStrings)
m_permissionsMap.remove(originString);
webPermissionController.removeEntryFromCache(ClientOrigin { SecurityOrigin::createFromString(originString)->data(), SecurityOriginData { } });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also bad.

void WebNotificationManager::removeAllPermissionsForTesting()
{
#if ENABLE(NOTIFICATIONS)
m_permissionsMap.clear();
auto& webPermissionController = downcast<WebPermissionController>(PermissionController::shared());
webPermissionController.clearCache();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, this seems wrong isn't it? We used to clear only notification permissions, now we clear ALL permissions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is a mistake. I'll fix this in the next upload of the patch.

NotificationClient::Permission resultPermission;
callOnMainRunLoopAndWait([&resultPermission, origin = origin->data().toString().isolatedCopy()] {
resultPermission = WebProcess::singleton().supplement<WebNotificationManager>()->policyForOrigin(origin);
auto* page = m_page ? m_page->corePage() : nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m_page is a main thread object and is thus not safe to access off the main thread like you're doing here.

if (!permissionState)
return;

switch (*permissionState) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we should have a helper function to convert one enum type to the other.

@@ -74,3 +79,7 @@ class WebPermissionController final : public CanMakeWeakPtr<WebPermissionControl
};

} // namespace WebCore

SPECIALIZE_TYPE_TRAITS_BEGIN(WebKit::WebPermissionController)
static bool isType(const WebCore::PermissionController&) { return true; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How could this possibly be correct? You're doing zero type validation?

PASS: service worker permissionState was prompt
PASS: document permissionState was prompt
PASS: service worker subscribe was error: NotAllowedError
PASS: document subscribe without user gesture was error: NotAllowedError
PASS: document subscribe with user gesture was error: NotAllowedError
PASS: document subscribe with consumed user gesture failed with user gesture error
PASS: document subscribe with user gesture consumed the user gesture
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difference in behavior here is worrisome. This is meant to be a refactoring that maintain pre-existing behavior. Mixing behavior changes and refactoring in the same patch is usually a recipe for having a bad time later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
New Bugs Unclassified bugs are placed in this component until the correct component can be determined.
Projects
None yet
6 participants