Skip to content
Permalink
Browse files
Add support for PermissionStatus.onchange
https://bugs.webkit.org/show_bug.cgi?id=244633

Reviewed by Chris Dumez and Youenn Fablet.

The PermissionStatus object returned by Permissions::query should
have an onchange event as specified by the Permissions API spec.
This patch adds this by adding a permissionChanged function to the
WKWebViewPrivate SPI which a client can call to indicate to WebKit
that a permission state has changed. WebPermissionController will
then query for the specified permission state and dispatch an
onchange event to the PermissionStatus object if the permission
state has indeed changed.

The permissionChanged function is a class/static function, so the
client needs to call it only once and WebKit will look through all
of the web processes and find the ones where the permission has
changed.

Currently, the onchange event is only supported in Window contexts.
A future patch will add support for Worker contexts.

An API test and Layout test were added to test this.

* LayoutTests/http/tests/notifications/permission/permission-status-onchange-event-expected.txt: Added.
* LayoutTests/http/tests/notifications/permission/permission-status-onchange-event.html: Added.
* LayoutTests/imported/w3c/web-platform-tests/permissions/idlharness.any-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/permissions/idlharness.any.worker-expected.txt:
* Source/WebCore/Headers.cmake:
* Source/WebCore/Modules/permissions/PermissionController.h:
* Source/WebCore/Modules/permissions/PermissionObserver.h:
* Source/WebCore/Modules/permissions/PermissionStatus.h:
* Source/WebCore/Modules/permissions/PermissionStatus.idl:
* Source/WebCore/Modules/permissions/Permissions.cpp:
(WebCore::Permissions::sourceFromContext):
(WebCore::Permissions::toPermissionName):
(WebCore::Permissions::query):
(WebCore::sourceFromContext): Deleted.
* Source/WebCore/Modules/permissions/Permissions.h:
* Source/WebCore/WebCore.xcodeproj/project.pbxproj:
* Source/WebKit/CMakeLists.txt:
* Source/WebKit/DerivedSources-input.xcfilelist:
* Source/WebKit/DerivedSources-output.xcfilelist:
* Source/WebKit/DerivedSources.make:
* Source/WebKit/Sources.txt:
* Source/WebKit/UIProcess/API/C/WKPage.cpp:
(WKPagePermissionChanged):
* Source/WebKit/UIProcess/API/C/WKPagePrivate.h:
* Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm:
(+[WKWebView _permissionChanged:forOrigin:]):
* Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h:
* Source/WebKit/UIProcess/WebPermissionControllerProxy.h:
* Source/WebKit/UIProcess/WebProcessProxy.cpp:
(WebKit::WebProcessProxy::permissionChanged):
(WebKit::WebProcessProxy::sendPermissionChanged):
* Source/WebKit/UIProcess/WebProcessProxy.h:
* Source/WebKit/WebKit.xcodeproj/project.pbxproj:
* Source/WebKit/WebProcess/WebCoreSupport/WebPermissionController.cpp:
(WebKit::WebPermissionController::create):
(WebKit::WebPermissionController::WebPermissionController):
(WebKit::WebPermissionController::~WebPermissionController):
(WebKit::WebPermissionController::query):
(WebKit::WebPermissionController::permissionChanged):
* Source/WebKit/WebProcess/WebCoreSupport/WebPermissionController.h:
* Source/WebKit/WebProcess/WebCoreSupport/WebPermissionController.messages.in: Copied from Source/WebCore/Modules/permissions/PermissionStatus.idl.
* Source/WebKit/WebProcess/WebProcess.cpp:
* Tools/TestWebKitAPI/Tests/WebKitCocoa/PermissionsAPI.mm:
(-[PermissionChangedTestAPIMessageHandler userContentController:didReceiveScriptMessage:]):
(-[PermissionChangedTestAPIUIDelegate _webView:queryPermission:forOrigin:completionHandler:]):
(TestWebKitAPI::TEST):
* Tools/WebKitTestRunner/TestController.cpp:
(WTR::TestController::grantNotificationPermission):
(WTR::TestController::denyNotificationPermission):

Canonical link: https://commits.webkit.org/254193@main
  • Loading branch information
RupinMittal authored and cdumez committed Sep 6, 2022
1 parent e347e3e commit 7f531f3e0265f2d2722545e3600d39eb08242190
Show file tree
Hide file tree
Showing 31 changed files with 320 additions and 31 deletions.
@@ -0,0 +1,14 @@
This test checks that the Permissions API on-change event works.

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".


No permission state is set
PASS permissionStatusState is "prompt"
Permission has been granted
PASS permissionStatusState is "granted"
PASS onChangeCalled became true
PASS successfullyParsed is true

TEST COMPLETE

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<script src="/resources/js-test-pre.js"></script>
<script src="/resources/notifications-test-pre.js"></script>
</head>
<body>
<script>

description("This test checks that the Permissions API on-change event works.")

jsTestIsAsync = true;

debug("No permission state is set");
navigator.permissions.query({ name: "notifications" }).then((permissionStatus)=>{
permissionStatusState = permissionStatus.state;
shouldBeEqualToString("permissionStatusState", "prompt");

onChangeCalled = false;
permissionStatus.onchange = () => {
permissionStatusState = permissionStatus.state;
shouldBeEqualToString("permissionStatusState", "granted");
onChangeCalled = true;
};

testRunner.grantWebNotificationPermission(testURL);
debug("Permission has been granted");
shouldBecomeEqual("onChangeCalled", "true", () => {
finishJSTest();
});
});

</script>
<script src="/resources/js-test-post.js"></script>
</body>
</html>
@@ -26,11 +26,11 @@ PASS PermissionStatus interface: existence and properties of interface prototype
PASS PermissionStatus interface: existence and properties of interface prototype object's "constructor" property
PASS PermissionStatus interface: existence and properties of interface prototype object's @@unscopables property
PASS PermissionStatus interface: attribute state
FAIL PermissionStatus interface: attribute onchange assert_true: The prototype object must have a property "onchange" expected true got false
PASS PermissionStatus interface: attribute onchange
PASS PermissionStatus must be primary interface of permissionStatus
PASS Stringification of permissionStatus
PASS PermissionStatus interface: permissionStatus must inherit property "state" with the proper type
FAIL PermissionStatus interface: permissionStatus must inherit property "onchange" with the proper type assert_inherits: property "onchange" not found in prototype chain
PASS PermissionStatus interface: permissionStatus must inherit property "onchange" with the proper type
PASS Permissions interface: existence and properties of interface object
PASS Permissions interface object length
PASS Permissions interface object name
@@ -26,11 +26,11 @@ PASS PermissionStatus interface: existence and properties of interface prototype
PASS PermissionStatus interface: existence and properties of interface prototype object's "constructor" property
PASS PermissionStatus interface: existence and properties of interface prototype object's @@unscopables property
PASS PermissionStatus interface: attribute state
FAIL PermissionStatus interface: attribute onchange assert_true: The prototype object must have a property "onchange" expected true got false
PASS PermissionStatus interface: attribute onchange
PASS PermissionStatus must be primary interface of permissionStatus
PASS Stringification of permissionStatus
PASS PermissionStatus interface: permissionStatus must inherit property "state" with the proper type
FAIL PermissionStatus interface: permissionStatus must inherit property "onchange" with the proper type assert_inherits: property "onchange" not found in prototype chain
PASS PermissionStatus interface: permissionStatus must inherit property "onchange" with the proper type
PASS Permissions interface: existence and properties of interface object
PASS Permissions interface object length
PASS Permissions interface object name
@@ -310,6 +310,7 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS
Modules/permissions/PermissionName.h
Modules/permissions/PermissionObserver.h
Modules/permissions/PermissionQuerySource.h
Modules/permissions/Permissions.h
Modules/permissions/PermissionState.h

Modules/plugins/PluginReplacement.h
@@ -25,28 +25,31 @@

#pragma once

#include "PermissionDescriptor.h"
#include <wtf/CompletionHandler.h>
#include <wtf/RefPtr.h>
#include <wtf/ThreadSafeRefCounted.h>

namespace WebCore {

enum class PermissionName : uint8_t;
enum class PermissionQuerySource : uint8_t;
enum class PermissionState : uint8_t;
class Page;
class PermissionObserver;
struct ClientOrigin;
struct PermissionDescriptor;
struct SecurityOriginData;

class PermissionController : public ThreadSafeRefCounted<PermissionController> {
public:
static PermissionController& shared();
WEBCORE_EXPORT static void setSharedController(Ref<PermissionController>&&);

virtual ~PermissionController() = default;
virtual void query(WebCore::ClientOrigin&&, PermissionDescriptor&&, Page*, PermissionQuerySource, CompletionHandler<void(std::optional<PermissionState>)>&&) = 0;
virtual void query(ClientOrigin&&, PermissionDescriptor, Page*, PermissionQuerySource, CompletionHandler<void(std::optional<PermissionState>)>&&) = 0;
virtual void addObserver(PermissionObserver&) = 0;
virtual void removeObserver(PermissionObserver&) = 0;
virtual void permissionChanged(PermissionName, const SecurityOriginData&) = 0;
protected:
PermissionController() = default;
};
@@ -56,9 +59,10 @@ class DummyPermissionController final : public PermissionController {
static Ref<DummyPermissionController> create() { return adoptRef(*new DummyPermissionController); }
private:
DummyPermissionController() = default;
void query(WebCore::ClientOrigin&&, PermissionDescriptor&&, Page*, PermissionQuerySource, CompletionHandler<void(std::optional<PermissionState>)>&& callback) final { callback({ }); }
void query(ClientOrigin&&, PermissionDescriptor, Page*, PermissionQuerySource, CompletionHandler<void(std::optional<PermissionState>)>&& callback) final { callback({ }); }
void addObserver(PermissionObserver&) final { }
void removeObserver(PermissionObserver&) final { }
void permissionChanged(PermissionName, const SecurityOriginData&) final { }
};

} // namespace WebCore
@@ -30,16 +30,20 @@

namespace WebCore {

class ScriptExecutionContext;
enum class PermissionState : uint8_t;
struct ClientOrigin;
struct PermissionDescriptor;

class PermissionObserver : public CanMakeWeakPtr<PermissionObserver> {
public:
virtual ~PermissionObserver() = default;

virtual PermissionState currentState() const = 0;
virtual void stateChanged(PermissionState) = 0;
virtual const ClientOrigin& origin() const = 0;
virtual const PermissionDescriptor& descriptor() const = 0;
virtual const ScriptExecutionContext* context() const = 0;
};

} // namespace WebCore
@@ -57,9 +57,11 @@ class PermissionStatus final : public PermissionObserver, public ActiveDOMObject
PermissionStatus(ScriptExecutionContext&, PermissionState, const PermissionDescriptor&);

// PermissionObserver
PermissionState currentState() const final { return m_state; }
void stateChanged(PermissionState) final;
const ClientOrigin& origin() const final { return m_origin; }
const PermissionDescriptor& descriptor() const final { return m_descriptor; }
const ScriptExecutionContext* context() const final { return ActiveDOMObject::scriptExecutionContext(); }

// ActiveDOMObject
const char* activeDOMObjectName() const final;
@@ -32,6 +32,5 @@
] interface PermissionStatus : EventTarget {
readonly attribute PermissionState state;
readonly attribute PermissionName name;
// FIXME: Add support for onchange.
// attribute EventHandler onchange;
attribute EventHandler onchange;
};
@@ -38,6 +38,7 @@
#include "Page.h"
#include "PermissionController.h"
#include "PermissionDescriptor.h"
#include "PermissionName.h"
#include "PermissionQuerySource.h"
#include "ScriptExecutionContext.h"
#include "SecurityOrigin.h"
@@ -46,8 +47,10 @@
#include "WorkerGlobalScope.h"
#include "WorkerLoaderProxy.h"
#include "WorkerThread.h"
#include <optional>
#include <wtf/IsoMallocInlines.h>
#include <wtf/TypeCasts.h>
#include <wtf/text/WTFString.h>

namespace WebCore {

@@ -84,7 +87,7 @@ static bool isAllowedByFeaturePolicy(const Document& document, PermissionName na
}
}

static std::optional<PermissionQuerySource> sourceFromContext(const ScriptExecutionContext& context)
std::optional<PermissionQuerySource> Permissions::sourceFromContext(const ScriptExecutionContext& context)
{
if (is<Document>(context))
return PermissionQuerySource::Window;
@@ -99,6 +102,20 @@ static std::optional<PermissionQuerySource> sourceFromContext(const ScriptExecut
return std::nullopt;
}


std::optional<PermissionName> Permissions::toPermissionName(const String& name)
{
if (name == "camera"_s)
return PermissionName::Camera;
if (name == "geolocation"_s)
return PermissionName::Geolocation;
if (name == "microphone"_s)
return PermissionName::Microphone;
if (name == "notifications"_s)
return PermissionName::Notifications;
return std::nullopt;
}

void Permissions::query(JSC::Strong<JSC::JSObject> permissionDescriptorValue, DOMPromiseDeferred<IDLInterface<PermissionStatus>>&& promise)
{
auto* context = m_navigator ? m_navigator->scriptExecutionContext() : nullptr;
@@ -141,7 +158,7 @@ void Permissions::query(JSC::Strong<JSC::JSObject> permissionDescriptorValue, DO
return;
}

PermissionController::shared().query(ClientOrigin { document->topOrigin().data(), WTFMove(originData) }, PermissionDescriptor { permissionDescriptor }, document->page(), *source, [document = Ref { *document }, permissionDescriptor, promise = WTFMove(promise)](auto permissionState) mutable {
PermissionController::shared().query(ClientOrigin { document->topOrigin().data(), WTFMove(originData) }, permissionDescriptor, document->page(), *source, [document = Ref { *document }, permissionDescriptor, promise = WTFMove(promise)](auto permissionState) mutable {
if (!permissionState)
promise.reject(Exception { NotSupportedError, "Permissions::query does not support this API"_s });
else
@@ -162,7 +179,7 @@ void Permissions::query(JSC::Strong<JSC::JSObject> permissionDescriptorValue, DO
return;
}

PermissionController::shared().query(ClientOrigin { document.topOrigin().data(), WTFMove(originData) }, PermissionDescriptor { permissionDescriptor }, document.page(), source, [contextIdentifier, permissionDescriptor, promise = WTFMove(promise)](auto permissionState) mutable {
PermissionController::shared().query(ClientOrigin { document.topOrigin().data(), WTFMove(originData) }, permissionDescriptor, document.page(), source, [contextIdentifier, permissionDescriptor, promise = WTFMove(promise)](auto permissionState) mutable {
ScriptExecutionContext::postTaskTo(contextIdentifier, [promise = WTFMove(promise), permissionState, permissionDescriptor](auto& context) mutable {
if (!permissionState)
promise.reject(Exception { NotSupportedError, "Permissions::query does not support this API"_s });
@@ -34,10 +34,17 @@ namespace JSC {
class JSObject;
} // namespace JSC

namespace WTF {
class String;
}

namespace WebCore {

class NavigatorBase;
class PermissionStatus;
class ScriptExecutionContext;
enum class PermissionName : uint8_t;
enum class PermissionQuerySource : uint8_t;

template<typename IDLType> class DOMPromiseDeferred;

@@ -49,6 +56,8 @@ class Permissions : public RefCounted<Permissions> {

NavigatorBase* navigator();
void query(JSC::Strong<JSC::JSObject>, DOMPromiseDeferred<IDLInterface<PermissionStatus>>&&);
WEBCORE_EXPORT static std::optional<PermissionQuerySource> sourceFromContext(const ScriptExecutionContext&);
WEBCORE_EXPORT static std::optional<PermissionName> toPermissionName(const String&);

private:
explicit Permissions(NavigatorBase&);
@@ -3079,7 +3079,7 @@
93A8061E1E03B585008A1F26 /* JSDoubleRange.h in Headers */ = {isa = PBXBuildFile; fileRef = 93A8061A1E03B585008A1F26 /* JSDoubleRange.h */; };
93A806201E03B585008A1F26 /* JSLongRange.h in Headers */ = {isa = PBXBuildFile; fileRef = 93A8061C1E03B585008A1F26 /* JSLongRange.h */; };
93B0A65326CDD14100AA21E4 /* NavigatorPermissions.h in Headers */ = {isa = PBXBuildFile; fileRef = 93B0A64A26CDD13E00AA21E4 /* NavigatorPermissions.h */; };
93B0A65426CDD14100AA21E4 /* Permissions.h in Headers */ = {isa = PBXBuildFile; fileRef = 93B0A64B26CDD13E00AA21E4 /* Permissions.h */; };
93B0A65426CDD14100AA21E4 /* Permissions.h in Headers */ = {isa = PBXBuildFile; fileRef = 93B0A64B26CDD13E00AA21E4 /* Permissions.h */; settings = {ATTRIBUTES = (Private, ); }; };
93B0A65626CDD14100AA21E4 /* PermissionStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = 93B0A64D26CDD13F00AA21E4 /* PermissionStatus.h */; };
93B0A65726CDD14100AA21E4 /* PermissionDescriptor.h in Headers */ = {isa = PBXBuildFile; fileRef = 93B0A64E26CDD13F00AA21E4 /* PermissionDescriptor.h */; settings = {ATTRIBUTES = (Private, ); }; };
93B0A65926CDD14100AA21E4 /* PermissionName.h in Headers */ = {isa = PBXBuildFile; fileRef = 93B0A65026CDD14000AA21E4 /* PermissionName.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -321,6 +321,7 @@ set(WebKit_MESSAGES_IN_FILES
WebProcess/WebCoreSupport/RemoteWebLockRegistry
WebProcess/WebCoreSupport/WebBroadcastChannelRegistry
WebProcess/WebCoreSupport/WebFileSystemStorageConnection
WebProcess/WebCoreSupport/WebPermissionController
WebProcess/WebCoreSupport/WebSpeechRecognitionConnection

WebProcess/WebPage/DrawingArea
@@ -247,6 +247,7 @@ $(PROJECT_DIR)/WebProcess/WebCoreSupport/RemoteWebLockRegistry.messages.in
$(PROJECT_DIR)/WebProcess/WebCoreSupport/WebBroadcastChannelRegistry.messages.in
$(PROJECT_DIR)/WebProcess/WebCoreSupport/WebDeviceOrientationUpdateProvider.messages.in
$(PROJECT_DIR)/WebProcess/WebCoreSupport/WebFileSystemStorageConnection.messages.in
$(PROJECT_DIR)/WebProcess/WebCoreSupport/WebPermissionController.messages.in
$(PROJECT_DIR)/WebProcess/WebCoreSupport/WebSpeechRecognitionConnection.messages.in
$(PROJECT_DIR)/WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.messages.in
$(PROJECT_DIR)/WebProcess/WebPage/DrawingArea.messages.in
@@ -575,6 +575,9 @@ $(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/WebPaymentCoordinatorProxyAdditionsM
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/WebPaymentCoordinatorProxyMessageReceiver.cpp
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/WebPaymentCoordinatorProxyMessages.h
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/WebPaymentCoordinatorProxyMessagesReplies.h
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/WebPermissionControllerMessageReceiver.cpp
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/WebPermissionControllerMessages.h
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/WebPermissionControllerMessagesReplies.h
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/WebPermissionControllerProxyMessageReceiver.cpp
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/WebPermissionControllerProxyMessages.h
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/WebPermissionControllerProxyMessagesReplies.h
@@ -225,6 +225,7 @@ MESSAGE_RECEIVERS = \
WebProcess/WebCoreSupport/WebBroadcastChannelRegistry \
WebProcess/WebCoreSupport/WebDeviceOrientationUpdateProvider \
WebProcess/WebCoreSupport/WebFileSystemStorageConnection \
WebProcess/WebCoreSupport/WebPermissionController \
WebProcess/WebCoreSupport/WebSpeechRecognitionConnection \
WebProcess/Speech/SpeechRecognitionRealtimeMediaSourceManager \
WebProcess/Storage/WebSharedWorkerContextManagerConnection \
@@ -875,6 +875,7 @@ SerializedTypeInfo.cpp
ServiceWorkerDownloadTaskMessageReceiver.cpp
WebBroadcastChannelRegistryMessageReceiver.cpp
WebLockRegistryProxyMessageReceiver.cpp
WebPermissionControllerMessageReceiver.cpp
WebPermissionControllerProxyMessageReceiver.cpp
WebSharedWorkerContextManagerConnectionMessageReceiver.cpp
WebSharedWorkerObjectConnectionMessageReceiver.cpp
@@ -85,7 +85,9 @@
#include <WebCore/ContentRuleListResults.h>
#include <WebCore/MockRealtimeMediaSourceCenter.h>
#include <WebCore/Page.h>
#include <WebCore/Permissions.h>
#include <WebCore/SSLKeyGenerator.h>
#include <WebCore/SecurityOrigin.h>
#include <WebCore/SecurityOriginData.h>
#include <WebCore/SerializedCryptoKeyWrap.h>
#include <WebCore/WindowFeatures.h>
@@ -350,6 +352,16 @@ bool WKPageTryClose(WKPageRef pageRef)
return toImpl(pageRef)->tryClose();
}

void WKPagePermissionChanged(WKStringRef permissionName, WKStringRef originString)
{
auto name = WebCore::Permissions::toPermissionName(toWTFString(permissionName));
if (!name)
return;

auto topOrigin = WebCore::SecurityOrigin::createFromString(toWTFString(originString))->data();
WebKit::WebProcessProxy::permissionChanged(*name, topOrigin);
}

void WKPageClose(WKPageRef pageRef)
{
toImpl(pageRef)->close();
@@ -212,6 +212,8 @@ WK_EXPORT void WKPageSetMediaCaptureReportingDelayForTesting(WKPageRef page, dou

WK_EXPORT void WKPageDispatchActivityStateUpdateForTesting(WKPageRef page);

WK_EXPORT void WKPagePermissionChanged(WKStringRef permissionName, WKStringRef originString);

#ifdef __cplusplus
}
#endif
@@ -30,6 +30,7 @@
#import "APIFormClient.h"
#import "APIFrameTreeNode.h"
#import "APIPageConfiguration.h"
#import "APISecurityOrigin.h"
#import "APISerializedScriptValue.h"
#import "CocoaImage.h"
#import "CompletionHandlerCallChecker.h"
@@ -76,6 +77,7 @@
#import "WKPreferencesInternal.h"
#import "WKProcessPoolInternal.h"
#import "WKSafeBrowsingWarning.h"
#import "WKSecurityOriginInternal.h"
#import "WKSharedAPICast.h"
#import "WKSnapshotConfiguration.h"
#import "WKUIDelegate.h"
@@ -128,6 +130,7 @@
#import <WebCore/JSDOMExceptionHandling.h>
#import <WebCore/LegacySchemeRegistry.h>
#import <WebCore/MIMETypeRegistry.h>
#import <WebCore/Permissions.h>
#import <WebCore/PlatformScreen.h>
#import <WebCore/RuntimeApplicationChecks.h>
#import <WebCore/Settings.h>
@@ -3938,6 +3941,15 @@ - (CGFloat)_overrideDeviceScaleFactor
return _page->customDeviceScaleFactor().value_or(0);
}

+ (void)_permissionChanged:(NSString *)permissionName forOrigin:(WKSecurityOrigin *)origin
{
auto name = WebCore::Permissions::toPermissionName(permissionName);
if (!name)
return;

WebKit::WebProcessProxy::permissionChanged(*name, origin->_securityOrigin->securityOrigin());
}

@end

@implementation WKWebView (WKDeprecated)

0 comments on commit 7f531f3

Please sign in to comment.