Changeset 286788 in webkit
- Timestamp:
- Dec 9, 2021, 10:58:34 AM (5 years ago)
- Location:
- trunk
- Files:
-
- 28 edited
- 3 copied
-
Source/WebKit/ChangeLog (modified) (1 diff)
-
Source/WebKit/Configurations/webpushtool.xcconfig (modified) (1 diff)
-
Source/WebKit/NetworkProcess/NetworkProcess.cpp (modified) (2 diffs)
-
Source/WebKit/NetworkProcess/NetworkProcess.h (modified) (2 diffs)
-
Source/WebKit/NetworkProcess/NetworkProcess.messages.in (modified) (1 diff)
-
Source/WebKit/NetworkProcess/Notifications/NetworkNotificationManager.cpp (modified) (3 diffs)
-
Source/WebKit/NetworkProcess/Notifications/NetworkNotificationManager.h (modified) (2 diffs)
-
Source/WebKit/Resources/webpushtool.entitlements (modified) (1 diff)
-
Source/WebKit/Shared/Cocoa/WebPushMessageCocoa.mm (copied) (copied from trunk/Source/WebKit/webpushd/webpushtool/WebPushToolConnection.h ) (1 diff)
-
Source/WebKit/Shared/PushMessageForTesting.h (copied) (copied from trunk/Source/WebKit/Shared/WebPushDaemonConstants.h ) (1 diff)
-
Source/WebKit/Shared/WebPushDaemonConstants.h (modified) (2 diffs)
-
Source/WebKit/Shared/WebPushMessage.h (copied) (copied from trunk/Source/WebKit/webpushd/webpushtool/WebPushToolConnection.h ) (1 diff)
-
Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm (modified) (2 diffs)
-
Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStorePrivate.h (modified) (1 diff)
-
Source/WebKit/UIProcess/Network/NetworkProcessProxy.cpp (modified) (1 diff)
-
Source/WebKit/UIProcess/Network/NetworkProcessProxy.h (modified) (2 diffs)
-
Source/WebKit/WebKit.xcodeproj/project.pbxproj (modified) (12 diffs)
-
Source/WebKit/webpushd/PushClientConnection.h (modified) (2 diffs)
-
Source/WebKit/webpushd/PushClientConnection.mm (modified) (2 diffs)
-
Source/WebKit/webpushd/WebPushDaemon.h (modified) (4 diffs)
-
Source/WebKit/webpushd/WebPushDaemon.mm (modified) (5 diffs)
-
Source/WebKit/webpushd/webpushtool/WebPushToolConnection.h (modified) (3 diffs)
-
Source/WebKit/webpushd/webpushtool/WebPushToolConnection.mm (modified) (2 diffs)
-
Source/WebKit/webpushd/webpushtool/WebPushToolMain.mm (modified) (5 diffs)
-
Tools/ChangeLog (modified) (1 diff)
-
Tools/TestWebKitAPI/Configurations/TestWebKitAPI-iOS.entitlements (modified) (1 diff)
-
Tools/TestWebKitAPI/Configurations/TestWebKitAPI-macOS-internal.entitlements (modified) (1 diff)
-
Tools/TestWebKitAPI/Configurations/TestWebKitAPI-macOS.entitlements (modified) (1 diff)
-
Tools/TestWebKitAPI/Tests/WebKitCocoa/PushAPI.mm (modified) (6 diffs)
-
Tools/TestWebKitAPI/Tests/WebKitCocoa/WebPushDaemon.mm (modified) (6 diffs)
-
Tools/TestWebKitAPI/cocoa/TestWKWebView.h (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebKit/ChangeLog
r286785 r286788 1 2021-12-09 Brady Eidson <beidson@apple.com> 2 3 Add ability to inject messages into webpushd 4 https://bugs.webkit.org/show_bug.cgi?id=233988 5 6 Reviewed by Alex Christensen. 7 8 Covered by API tests. 9 10 This patch: 11 - Adds WKWebsiteDataStore SPI to fetch pending push messages for the embedding application 12 - Gives webpushd code to inject a push message for a given bundle identifier 13 - Gives webpushtool the ability to send one of these fake messages 14 - Gives webpushtool the ability to reconnect to the next daemon instance after the current connection is dropped 15 - Tests the injection and fetching of push messages via TestWebKitAPI 16 17 * Configurations/webpushtool.xcconfig: 18 * WebKit.xcodeproj/project.pbxproj: 19 * Resources/webpushtool.entitlements: 20 21 * NetworkProcess/NetworkProcess.cpp: 22 (WebKit::NetworkProcess::getPendingPushMessages): 23 (WebKit::NetworkProcess::processPushMessage): 24 * NetworkProcess/NetworkProcess.h: 25 * NetworkProcess/NetworkProcess.messages.in: 26 27 * NetworkProcess/Notifications/NetworkNotificationManager.cpp: 28 (WebKit::NetworkNotificationManager::getPendingPushMessages): 29 (WebKit::ReplyCaller<Vector<WebPushMessage>::callReply): 30 * NetworkProcess/Notifications/NetworkNotificationManager.h: 31 32 * Shared/Cocoa/WebPushMessageCocoa.mm: Copied from Source/WebKit/webpushd/webpushtool/WebPushToolConnection.h. 33 (WebKit::WebPushMessage::fromDictionary): 34 (WebKit::WebPushMessage::toDictionary const): 35 36 * Shared/PushMessageForTesting.h: Copied from Source/WebKit/Shared/WebPushDaemonConstants.h. 37 (WebKit::WebPushD::PushMessageForTesting::encode const): 38 (WebKit::WebPushD::PushMessageForTesting::decode): 39 40 * Shared/WebPushDaemonConstants.h: 41 (WebKit::WebPushD::messageTypeSendsReply): 42 43 * Shared/WebPushMessage.h: Copied from Source/WebKit/webpushd/webpushtool/WebPushToolConnection.h. 44 (WebKit::WebPushMessage::encode const): 45 (WebKit::WebPushMessage::decode): 46 47 * UIProcess/API/Cocoa/WKWebsiteDataStore.mm: 48 (-[WKWebsiteDataStore _getPendingPushMessages:]): 49 (-[WKWebsiteDataStore _processPushMessage:completionHandler:]): 50 (-[WKWebsiteDataStore _processPushMessage:registration:completionHandler:]): Deleted. 51 * UIProcess/API/Cocoa/WKWebsiteDataStorePrivate.h: 52 53 * UIProcess/Network/NetworkProcessProxy.cpp: 54 (WebKit::NetworkProcessProxy::getPendingPushMessages): 55 (WebKit::NetworkProcessProxy::processPushMessage): 56 * UIProcess/Network/NetworkProcessProxy.h: 57 58 * webpushd/PushClientConnection.h: 59 * webpushd/PushClientConnection.mm: 60 (WebPushD::ClientConnection::hostAppHasPushEntitlement): 61 (WebPushD::ClientConnection::hostAppHasPushInjectEntitlement): 62 (WebPushD::ClientConnection::hostHasEntitlement): 63 (WebPushD::ClientConnection::broadcastDebugMessage): 64 65 * webpushd/WebPushDaemon.h: 66 * webpushd/WebPushDaemon.mm: 67 (WebPushD::MessageInfo::injectPushMessageForTesting::encodeReply): 68 (WebPushD::MessageInfo::getPendingPushMessages::encodeReply): 69 (WebPushD::Daemon::decodeAndHandleMessage): 70 (WebPushD::Daemon::injectPushMessageForTesting): 71 (WebPushD::Daemon::getPendingPushMessages): 72 73 * webpushd/webpushtool/WebPushToolConnection.h: 74 (WebPushTool::Connection::setPushMessage): 75 76 * webpushd/webpushtool/WebPushToolConnection.mm: 77 (WebPushTool::Connection::startAction): 78 (WebPushTool::Connection::sendPushMessage): 79 80 * webpushd/webpushtool/WebPushToolMain.mm: 81 (printUsageAndTerminate): 82 (pushMessageFromArguments): 83 (main): 84 1 85 2021-12-09 J Pascoe <j_pascoe@apple.com> 2 86 -
trunk/Source/WebKit/Configurations/webpushtool.xcconfig
r286777 r286788 30 30 EXCLUDED_SOURCE_FILE_NAMES[sdk=watch*] = *; 31 31 32 OTHER_LDFLAGS = - l WTF -framework Foundation -framework CoreFoundation;32 OTHER_LDFLAGS = -framework WebKit -framework JavaScriptCore 33 33 LIBRARY_SEARCH_PATHS = $(BUILT_PRODUCTS_DIR); 34 34 -
trunk/Source/WebKit/NetworkProcess/NetworkProcess.cpp
r286777 r286788 59 59 #include "WebPageProxyMessages.h" 60 60 #include "WebProcessPoolMessages.h" 61 #include "WebPushMessage.h" 61 62 #include "WebResourceLoadStatisticsStore.h" 62 63 #include "WebSWOriginStore.h" … … 2556 2557 } 2557 2558 2558 void NetworkProcess::processPushMessage(PAL::SessionID sessionID, const std::optional<IPC::DataReference>& ipcData, URL&& registrationURL, CompletionHandler<void(bool)>&& callback) 2559 { 2560 std::optional<Vector<uint8_t>> data; 2561 if (ipcData) 2562 data = Vector<uint8_t> { ipcData->data(), ipcData->size() }; 2563 swServerForSession(sessionID).processPushMessage(WTFMove(data), WTFMove(registrationURL), WTFMove(callback)); 2559 void NetworkProcess::getPendingPushMessages(PAL::SessionID sessionID, CompletionHandler<void(const Vector<WebPushMessage>&)>&& callback) 2560 { 2561 #if ENABLE(BUILT_IN_NOTIFICATIONS) 2562 if (auto* session = networkSession(sessionID)) { 2563 session->notificationManager().getPendingPushMessages(WTFMove(callback)); 2564 return; 2565 } 2566 #endif 2567 callback({ }); 2568 } 2569 2570 void NetworkProcess::processPushMessage(PAL::SessionID sessionID, WebPushMessage&& pushMessage, CompletionHandler<void(bool)>&& callback) 2571 { 2572 swServerForSession(sessionID).processPushMessage(WTFMove(pushMessage.pushData), WTFMove(pushMessage.registrationURL), WTFMove(callback)); 2564 2573 } 2565 2574 #endif // ENABLE(SERVICE_WORKER) -
trunk/Source/WebKit/NetworkProcess/NetworkProcess.h
r286777 r286788 118 118 enum class WebsiteDataType : uint32_t; 119 119 struct NetworkProcessCreationParameters; 120 struct WebPushMessage; 120 121 struct WebsiteDataStoreParameters; 121 122 … … 397 398 398 399 #if ENABLE(SERVICE_WORKER) 399 void processPushMessage(PAL::SessionID, const std::optional<IPC::DataReference>&, URL&&, CompletionHandler<void(bool)>&&); 400 void getPendingPushMessages(PAL::SessionID, CompletionHandler<void(const Vector<WebPushMessage>&)>&&); 401 void processPushMessage(PAL::SessionID, WebPushMessage&&, CompletionHandler<void(bool)>&&); 400 402 #endif 401 403 -
trunk/Source/WebKit/NetworkProcess/NetworkProcess.messages.in
r286777 r286788 208 208 209 209 #if ENABLE(SERVICE_WORKER) 210 ProcessPushMessage(PAL::SessionID sessionID, std::optional<IPC::DataReference> data, URL registrationURL) -> (bool didSucceed) Async 210 GetPendingPushMessages(PAL::SessionID sessionID) -> (Vector<WebKit::WebPushMessage> messages) Async 211 ProcessPushMessage(PAL::SessionID sessionID, struct WebKit::WebPushMessage pushMessage) -> (bool didSucceed) Async 211 212 #endif 212 213 DeletePushAndNotificationRegistration(PAL::SessionID sessionID, struct WebCore::SecurityOriginData origin) -> (String errorMessage) Async -
trunk/Source/WebKit/NetworkProcess/Notifications/NetworkNotificationManager.cpp
r286777 r286788 33 33 #include "NetworkSession.h" 34 34 #include "WebPushDaemonConnectionConfiguration.h" 35 #include "WebPushMessage.h" 35 36 #include <WebCore/SecurityOriginData.h> 36 37 … … 87 88 88 89 sendMessageWithReply<WebPushD::MessageType::GetOriginsWithPushAndNotificationPermissions>(WTFMove(replyHandler)); 90 } 91 92 void NetworkNotificationManager::getPendingPushMessages(CompletionHandler<void(const Vector<WebPushMessage>&)>&& completionHandler) 93 { 94 CompletionHandler<void(Vector<WebPushMessage>&&)> replyHandler = [completionHandler = WTFMove(completionHandler)] (Vector<WebPushMessage>&& messages) mutable { 95 completionHandler(WTFMove(messages)); 96 }; 97 98 sendMessageWithReply<WebPushD::MessageType::GetPendingPushMessages>(WTFMove(replyHandler)); 89 99 } 90 100 … … 187 197 }; 188 198 199 template<> struct ReplyCaller<Vector<WebPushMessage>&&> { 200 static void callReply(Daemon::Decoder&& decoder, CompletionHandler<void(Vector<WebPushMessage>&&)>&& completionHandler) 201 { 202 std::optional<Vector<WebPushMessage>> messages; 203 decoder >> messages; 204 if (!messages) 205 return completionHandler({ }); 206 completionHandler(WTFMove(*messages)); 207 } 208 }; 209 189 210 template<WebPushD::MessageType messageType, typename... Args, typename... ReplyArgs> 190 211 void NetworkNotificationManager::sendMessageWithReply(CompletionHandler<void(ReplyArgs...)>&& completionHandler, Args&&... args) const -
trunk/Source/WebKit/NetworkProcess/Notifications/NetworkNotificationManager.h
r286777 r286788 30 30 #include "NotificationManagerMessageHandler.h" 31 31 #include "WebPushDaemonConnection.h" 32 #include "WebPushMessage.h" 32 33 #include <WebCore/NotificationDirection.h> 33 34 #include <wtf/text/WTFString.h> … … 53 54 void deletePushAndNotificationRegistration(const WebCore::SecurityOriginData&, CompletionHandler<void(const String&)>&&); 54 55 void getOriginsWithPushAndNotificationPermissions(CompletionHandler<void(const Vector<WebCore::SecurityOriginData>&)>&&); 56 void getPendingPushMessages(CompletionHandler<void(const Vector<WebPushMessage>&)>&&); 55 57 56 58 private: -
trunk/Source/WebKit/Resources/webpushtool.entitlements
r286777 r286788 5 5 <key>com.apple.private.webkit.webpush</key> 6 6 <true/> 7 <key>com.apple.private.webkit.webpush.inject</key> 8 <true/> 7 9 </dict> 8 10 </plist> -
trunk/Source/WebKit/Shared/Cocoa/WebPushMessageCocoa.mm
r286787 r286788 24 24 */ 25 25 26 #pragma once 26 #import "config.h" 27 #import "WebPushMessage.h" 27 28 28 #include <memory> 29 #include <wtf/RetainPtr.h> 30 #include <wtf/WeakPtr.h> 31 #include <wtf/spi/darwin/XPCSPI.h> 29 #import <wtf/RetainPtr.h> 32 30 33 namespace Web PushTool{31 namespace WebKit { 34 32 35 enum class Action { 36 StreamDebugMessages, 37 }; 33 #define WebKitPushDataKey @"WebKitPushData" 34 #define WebKitPushRegistrationURLKey @"WebKitPushRegistrationURL" 38 35 39 enum class PreferTestService : bool { 40 Yes, 41 No, 42 }; 36 std::optional<WebPushMessage> WebPushMessage::fromDictionary(NSDictionary *dictionary) 37 { 38 NSURL *url = [dictionary objectForKey:WebKitPushRegistrationURLKey]; 39 if (!url || ![url isKindOfClass:[NSURL class]]) 40 return std::nullopt; 43 41 44 enum class Reconnect : bool { 45 Yes, 46 No, 47 }; 42 NSData *pushData = [dictionary objectForKey:WebKitPushDataKey]; 43 if (!pushData || ![pushData isKindOfClass:[NSData class]]) 44 return std::nullopt; 48 45 49 class Connection : public CanMakeWeakPtr<Connection>{50 WTF_MAKE_FAST_ALLOCATED;51 public: 52 static std::unique_ptr<Connection> create(Action, PreferTestService, Reconnect);53 Connection(Action, PreferTestService, Reconnect); 46 return { { 47 Vector<uint8_t> { static_cast<const uint8_t*>(pushData.bytes), pushData.length }, 48 URL { url } 49 } }; 50 } 54 51 55 void connectToService(); 52 NSDictionary *WebPushMessage::toDictionary() const 53 { 54 auto nsData = adoptNS([[NSData alloc] initWithBytes:pushData.data() length:pushData.size()]); 55 return @{ 56 WebKitPushDataKey : nsData.get(), 57 WebKitPushRegistrationURLKey : (NSURL *)registrationURL 58 }; 59 } 56 60 57 private: 58 void messageReceived(xpc_object_t); 59 void connectionDropped(); 60 61 void startAction(); 62 void startDebugStreamAction(); 63 64 void sendAuditToken(); 65 66 Action m_action; 67 bool m_reconnect { false }; 68 RetainPtr<xpc_connection_t> m_connection; 69 const char* m_serviceName; 70 }; 71 72 } // namespace WebPushTool 61 } // namespace WebKit -
trunk/Source/WebKit/Shared/PushMessageForTesting.h
r286787 r286788 26 26 #pragma once 27 27 28 #include <wtf/URL.h> 29 #include <wtf/text/WTFString.h> 30 28 31 namespace WebKit::WebPushD { 29 32 30 constexpr const char* protocolVersionKey = "protocol version"; 31 constexpr uint64_t protocolVersionValue = 1;32 constexpr const char* protocolEncodedMessageKey = "encoded message";33 struct PushMessageForTesting { 34 template<class Encoder> void encode(Encoder&) const; 35 template<class Decoder> static std::optional<PushMessageForTesting> decode(Decoder&); 33 36 34 constexpr const char* protocolDebugMessageKey { "debug message" }; 35 constexpr const char* protocolDebugMessageLevelKey { "debug message level" }; 36 37 constexpr const char* protocolMessageTypeKey { "message type" }; 38 enum class MessageType : uint8_t { 39 EchoTwice = 1, 40 RequestSystemNotificationPermission, 41 DeletePushAndNotificationRegistration, 42 GetOriginsWithPushAndNotificationPermissions, 43 SetDebugModeIsEnabled, 44 UpdateConnectionConfiguration, 37 String targetAppCodeSigningIdentifier; 38 URL registrationURL; 39 String message; 45 40 }; 46 41 47 inline bool messageTypeSendsReply(MessageType messageType) 42 template<class Encoder> 43 void PushMessageForTesting::encode(Encoder& encoder) const 48 44 { 49 switch (messageType) { 50 case MessageType::EchoTwice: 51 case MessageType::GetOriginsWithPushAndNotificationPermissions: 52 case MessageType::DeletePushAndNotificationRegistration: 53 case MessageType::RequestSystemNotificationPermission: 54 return true; 55 case MessageType::SetDebugModeIsEnabled: 56 case MessageType::UpdateConnectionConfiguration: 57 return false; 58 } 59 ASSERT_NOT_REACHED(); 60 return false; 45 encoder << targetAppCodeSigningIdentifier << registrationURL << message; 46 } 47 48 template<class Decoder> 49 std::optional<PushMessageForTesting> PushMessageForTesting::decode(Decoder& decoder) 50 { 51 std::optional<String> targetAppCodeSigningIdentifier; 52 decoder >> targetAppCodeSigningIdentifier; 53 if (!targetAppCodeSigningIdentifier) 54 return std::nullopt; 55 56 std::optional<URL> registrationURL; 57 decoder >> registrationURL; 58 if (!registrationURL) 59 return std::nullopt; 60 61 std::optional<String> message; 62 decoder >> message; 63 if (!message) 64 return std::nullopt; 65 66 return { { 67 WTFMove(*targetAppCodeSigningIdentifier), 68 WTFMove(*registrationURL), 69 WTFMove(*message), 70 } }; 61 71 } 62 72 -
trunk/Source/WebKit/Shared/WebPushDaemonConstants.h
r286777 r286788 43 43 SetDebugModeIsEnabled, 44 44 UpdateConnectionConfiguration, 45 InjectPushMessageForTesting, 46 GetPendingPushMessages, 45 47 }; 46 48 … … 52 54 case MessageType::DeletePushAndNotificationRegistration: 53 55 case MessageType::RequestSystemNotificationPermission: 56 case MessageType::GetPendingPushMessages: 57 case MessageType::InjectPushMessageForTesting: 54 58 return true; 55 59 case MessageType::SetDebugModeIsEnabled: -
trunk/Source/WebKit/Shared/WebPushMessage.h
r286787 r286788 26 26 #pragma once 27 27 28 #include <memory> 29 #include <wtf/RetainPtr.h> 30 #include <wtf/WeakPtr.h> 31 #include <wtf/spi/darwin/XPCSPI.h> 28 #include <optional> 29 #include <wtf/URL.h> 30 #include <wtf/Vector.h> 32 31 33 namespace WebPushTool { 32 OBJC_CLASS NSDictionary; 34 33 35 enum class Action { 36 StreamDebugMessages, 34 namespace WebKit { 35 36 struct WebPushMessage { 37 template<class Encoder> void encode(Encoder&) const; 38 template<class Decoder> static std::optional<WebPushMessage> decode(Decoder&); 39 40 Vector<uint8_t> pushData; 41 URL registrationURL; 42 43 #if PLATFORM(COCOA) 44 static std::optional<WebPushMessage> fromDictionary(NSDictionary *); 45 NSDictionary *toDictionary() const; 46 #endif 37 47 }; 38 48 39 enum class PreferTestService : bool { 40 Yes, 41 No, 42 }; 49 template<class Encoder> 50 void WebPushMessage::encode(Encoder& encoder) const 51 { 52 encoder << pushData << registrationURL; 53 } 43 54 44 enum class Reconnect : bool { 45 Yes, 46 No, 47 }; 55 template<class Decoder> 56 std::optional<WebPushMessage> WebPushMessage::decode(Decoder& decoder) 57 { 58 std::optional<Vector<uint8_t>> pushData; 59 decoder >> pushData; 60 if (!pushData) 61 return std::nullopt; 48 62 49 class Connection : public CanMakeWeakPtr<Connection> { 50 WTF_MAKE_FAST_ALLOCATED; 51 public: 52 static std::unique_ptr<Connection> create(Action, PreferTestService, Reconnect); 53 Connection(Action, PreferTestService, Reconnect); 63 std::optional<URL> registrationURL; 64 decoder >> registrationURL; 65 if (!registrationURL) 66 return std::nullopt; 54 67 55 void connectToService(); 68 return { { 69 WTFMove(*pushData), 70 WTFMove(*registrationURL) 71 } }; 72 } 56 73 57 private: 58 void messageReceived(xpc_object_t); 59 void connectionDropped(); 60 61 void startAction(); 62 void startDebugStreamAction(); 63 64 void sendAuditToken(); 65 66 Action m_action; 67 bool m_reconnect { false }; 68 RetainPtr<xpc_connection_t> m_connection; 69 const char* m_serviceName; 70 }; 71 72 } // namespace WebPushTool 74 } // namespace WebKit -
trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm
r286777 r286788 40 40 #import "WKWebsiteDataRecordInternal.h" 41 41 #import "WebPageProxy.h" 42 #import "WebPushMessage.h" 42 43 #import "WebResourceLoadStatisticsStore.h" 43 44 #import "WebsiteDataFetchOption.h" … … 762 763 } 763 764 764 -(void)_ processPushMessage:(NSData*) message registration:(NSURL *)registration completionHandler:(void(^)(bool wasProcessed))completionHandler765 -(void)_getPendingPushMessages:(void(^)(NSArray<NSDictionary *> *))completionHandler 765 766 { 766 767 #if ENABLE(SERVICE_WORKER) 767 std::optional<Span<const uint8_t>> data; 768 if (message) 769 data = Span<const uint8_t> { reinterpret_cast<const uint8_t*>(message.bytes), message.length }; 770 _websiteDataStore->networkProcess().processPushMessage(_websiteDataStore->sessionID(), data, registration, [completionHandler = makeBlockPtr(completionHandler)] (bool wasProcessed) { 768 _websiteDataStore->networkProcess().getPendingPushMessages(_websiteDataStore->sessionID(), [completionHandler = makeBlockPtr(completionHandler)] (const Vector<WebKit::WebPushMessage>& messages) { 769 auto result = adoptNS([[NSMutableArray alloc] initWithCapacity:messages.size()]); 770 for (auto& message : messages) 771 [result addObject:message.toDictionary()]; 772 773 completionHandler(result.get()); 774 }); 775 #endif 776 } 777 778 -(void)_processPushMessage:(NSDictionary *)pushMessageDictionary completionHandler:(void(^)(bool wasProcessed))completionHandler 779 { 780 #if ENABLE(SERVICE_WORKER) 781 auto pushMessage = WebKit::WebPushMessage::fromDictionary(pushMessageDictionary); 782 if (!pushMessage) { 783 completionHandler(false); 784 return; 785 } 786 787 _websiteDataStore->networkProcess().processPushMessage(_websiteDataStore->sessionID(), *pushMessage, [completionHandler = makeBlockPtr(completionHandler)] (bool wasProcessed) { 771 788 completionHandler(wasProcessed); 772 789 }); -
trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStorePrivate.h
r286777 r286788 109 109 110 110 -(bool)_hasServiceWorkerBackgroundActivityForTesting WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA)); 111 -(void)_processPushMessage:(NSData*)data registration:(NSURL *)registration completionHandler:(void(^)(bool))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA)); 111 -(void)_getPendingPushMessages:(void(^)(NSArray<NSDictionary *> *))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA)); 112 -(void)_processPushMessage:(NSDictionary *)pushMessage completionHandler:(void(^)(bool))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA)); 112 113 -(void)_deletePushAndNotificationRegistration:(WKSecurityOrigin *)securityOrigin completionHandler:(void(^)(NSError *))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA)); 113 114 -(void)_getOriginsWithPushAndNotificationPermissions:(void(^)(NSSet<WKSecurityOrigin *> *))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA)); -
trunk/Source/WebKit/UIProcess/Network/NetworkProcessProxy.cpp
r286777 r286788 1693 1693 1694 1694 #if ENABLE(SERVICE_WORKER) 1695 void NetworkProcessProxy::processPushMessage(PAL::SessionID sessionID, std::optional<Span<const uint8_t>> data, const URL& registrationURL, CompletionHandler<void(bool wasProcessed)>&& callback) 1696 { 1697 std::optional<IPC::DataReference> ipcData; 1698 if (data) 1699 ipcData = IPC::DataReference { data->data(), data->size() }; 1700 sendWithAsyncReply(Messages::NetworkProcess::ProcessPushMessage { sessionID, ipcData, registrationURL }, WTFMove(callback)); 1695 void NetworkProcessProxy::getPendingPushMessages(PAL::SessionID sessionID, CompletionHandler<void(const Vector<WebPushMessage>&)>&& completionHandler) 1696 { 1697 sendWithAsyncReply(Messages::NetworkProcess::GetPendingPushMessages { sessionID }, WTFMove(completionHandler)); 1698 } 1699 1700 void NetworkProcessProxy::processPushMessage(PAL::SessionID sessionID, const WebPushMessage& pushMessage, CompletionHandler<void(bool wasProcessed)>&& callback) 1701 { 1702 sendWithAsyncReply(Messages::NetworkProcess::ProcessPushMessage { sessionID, pushMessage }, WTFMove(callback)); 1701 1703 } 1702 1704 #endif // ENABLE(SERVICE_WORKER) -
trunk/Source/WebKit/UIProcess/Network/NetworkProcessProxy.h
r286777 r286788 93 93 struct NetworkProcessCreationParameters; 94 94 struct ResourceLoadInfo; 95 struct WebPushMessage; 95 96 struct WebsiteData; 96 97 struct WebsiteDataStoreParameters; … … 274 275 275 276 #if ENABLE(SERVICE_WORKER) 276 void processPushMessage(PAL::SessionID, std::optional<Span<const uint8_t>>, const URL&, CompletionHandler<void(bool wasProcessed)>&&); 277 void getPendingPushMessages(PAL::SessionID, CompletionHandler<void(const Vector<WebPushMessage>&)>&&); 278 void processPushMessage(PAL::SessionID, const WebPushMessage&, CompletionHandler<void(bool wasProcessed)>&&); 277 279 #endif 278 280 -
trunk/Source/WebKit/WebKit.xcodeproj/project.pbxproj
r286777 r286788 1035 1035 517B5F85275E97B6002DC22D /* AppBundleRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 5160E954274B887100567388 /* AppBundleRequest.h */; }; 1036 1036 517B5F86275E97B6002DC22D /* MockAppBundleRegistry.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5160E95F274C2A3F00567388 /* MockAppBundleRegistry.mm */; }; 1037 517B5F95275EBA63002DC22D /* PushMessageForTesting.h in Headers */ = {isa = PBXBuildFile; fileRef = 517B5F94275EBA62002DC22D /* PushMessageForTesting.h */; }; 1038 517B5F97275EC5E5002DC22D /* WebPushMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 517B5F96275EC5E5002DC22D /* WebPushMessage.h */; }; 1039 517B5F99275EC601002DC22D /* WebPushMessageCocoa.mm in Sources */ = {isa = PBXBuildFile; fileRef = 517B5F98275EC600002DC22D /* WebPushMessageCocoa.mm */; }; 1040 517B5F9A275F3A86002DC22D /* ArgumentCoders.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1A3D610413A7F03A00F95D4E /* ArgumentCoders.cpp */; }; 1041 517B5F9B275F3A98002DC22D /* DaemonUtilities.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5C1579F92717AF5000ED5280 /* DaemonUtilities.mm */; }; 1042 517B5F9C275F3C37002DC22D /* DaemonEncoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5C1579E827172A8A00ED5280 /* DaemonEncoder.cpp */; }; 1037 1043 517CF0E3163A486C00C2950E /* NetworkProcessConnectionMessageReceiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 517CF0E1163A486C00C2950E /* NetworkProcessConnectionMessageReceiver.cpp */; }; 1038 1044 517CF0E3163A486C00C2950F /* CacheStorageEngineConnectionMessageReceiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 517CF0E1163A486C00C2950F /* CacheStorageEngineConnectionMessageReceiver.cpp */; }; … … 2140 2146 remoteGlobalIDString = 517B5F5A275A8D3E002DC22D; 2141 2147 remoteInfo = webpushtool; 2148 }; 2149 519797C627627B1D00BE0F83 /* PBXContainerItemProxy */ = { 2150 isa = PBXContainerItemProxy; 2151 containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; 2152 proxyType = 1; 2153 remoteGlobalIDString = E1AC2E2720F7B94C00B0897D; 2154 remoteInfo = "Unlock Keychain"; 2155 }; 2156 519797C827627B2300BE0F83 /* PBXContainerItemProxy */ = { 2157 isa = PBXContainerItemProxy; 2158 containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; 2159 proxyType = 1; 2160 remoteGlobalIDString = 8DC2EF4F0486A6940098B216; 2161 remoteInfo = WebKit; 2142 2162 }; 2143 2163 5742A2E72535613F00B7BA14 /* PBXContainerItemProxy */ = { … … 4410 4430 517B5F72275E9609002DC22D /* WebPushDaemonMain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebPushDaemonMain.h; sourceTree = "<group>"; }; 4411 4431 517B5F77275E9795002DC22D /* webpushd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = webpushd.cpp; sourceTree = "<group>"; }; 4432 517B5F94275EBA62002DC22D /* PushMessageForTesting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PushMessageForTesting.h; sourceTree = "<group>"; }; 4433 517B5F96275EC5E5002DC22D /* WebPushMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebPushMessage.h; sourceTree = "<group>"; }; 4434 517B5F98275EC600002DC22D /* WebPushMessageCocoa.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WebPushMessageCocoa.mm; sourceTree = "<group>"; }; 4412 4435 517CF0E1163A486C00C2950E /* NetworkProcessConnectionMessageReceiver.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = NetworkProcessConnectionMessageReceiver.cpp; path = DerivedSources/WebKit/NetworkProcessConnectionMessageReceiver.cpp; sourceTree = BUILT_PRODUCTS_DIR; }; 4413 4436 517CF0E1163A486C00C2950F /* CacheStorageEngineConnectionMessageReceiver.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CacheStorageEngineConnectionMessageReceiver.cpp; path = DerivedSources/WebKit/CacheStorageEngineConnectionMessageReceiver.cpp; sourceTree = BUILT_PRODUCTS_DIR; }; … … 7137 7160 E1CC1B8E12D7EADF00625838 /* PrintInfo.h */, 7138 7161 463FD4811EB94EAD00A2982C /* ProcessTerminationReason.h */, 7162 517B5F94275EBA62002DC22D /* PushMessageForTesting.h */, 7139 7163 9B1229D023FF2A5E008CA751 /* RemoteAudioDestinationIdentifier.h */, 7140 7164 7203449B26A6C476000A5F54 /* RenderingUpdateID.h */, … … 7243 7267 517B5F2D2757382A002DC22D /* WebPushDaemonConnectionConfiguration.h */, 7244 7268 512CD6992721F04900F7F8EC /* WebPushDaemonConstants.h */, 7269 517B5F96275EC5E5002DC22D /* WebPushMessage.h */, 7245 7270 5C8DD37F1FE4519200F2A556 /* WebsiteAutoplayPolicy.h */, 7246 7271 5C8DD3811FE455CA00F2A556 /* WebsiteAutoplayQuirk.h */, … … 8939 8964 465250E51ECF52CD002025CB /* WebKit2InitializeCocoa.mm */, 8940 8965 1DE076D92460CCBD00B211E8 /* WebPreferencesDefaultValuesCocoa.mm */, 8966 517B5F98275EC600002DC22D /* WebPushMessageCocoa.mm */, 8941 8967 37C4C0921814B3AF003688B9 /* WKNSArray.h */, 8942 8968 37C4C0911814B3AF003688B9 /* WKNSArray.mm */, … … 13014 13040 839902031BE9A02B000F3653 /* NetworkLoad.h in Headers */, 13015 13041 83D454D71BE9D3C4006C93BD /* NetworkLoadClient.h in Headers */, 13042 517B5F97275EC5E5002DC22D /* WebPushMessage.h in Headers */, 13016 13043 839149651BEA838500D2D953 /* NetworkLoadParameters.h in Headers */, 13017 13044 E47FC8A025B8331C005495FC /* NetworkLoadScheduler.h in Headers */, … … 13521 13548 517A53101F47A86200DCDC0A /* WebSWClientConnectionMessages.h in Headers */, 13522 13549 466BC03C1FA266DA002FA9C1 /* WebSWContextManagerConnection.h in Headers */, 13550 517B5F95275EBA63002DC22D /* PushMessageForTesting.h in Headers */, 13523 13551 460F48901F996F7100CF4B87 /* WebSWContextManagerConnectionMessages.h in Headers */, 13524 13552 C11A9ECC214035F800CFB20A /* WebSwitchingGPUClient.h in Headers */, … … 14042 14070 ); 14043 14071 dependencies = ( 14072 519797C927627B2300BE0F83 /* PBXTargetDependency */, 14073 519797C727627B1D00BE0F83 /* PBXTargetDependency */, 14044 14074 ); 14045 14075 name = webpushtool; … … 15086 15116 buildActionMask = 2147483647; 15087 15117 files = ( 15118 517B5F9A275F3A86002DC22D /* ArgumentCoders.cpp in Sources */, 15088 15119 517B5F68275A9A78002DC22D /* WebPushToolConnection.mm in Sources */, 15120 517B5F9B275F3A98002DC22D /* DaemonUtilities.mm in Sources */, 15121 517B5F9C275F3C37002DC22D /* DaemonEncoder.cpp in Sources */, 15089 15122 517B5F65275A8D7F002DC22D /* WebPushToolMain.mm in Sources */, 15090 15123 ); … … 15182 15215 C1A152D724E5A29A00978C8B /* HandleXPCEndpointMessages.mm in Sources */, 15183 15216 2749F6442146561B008380BF /* InjectedBundleNodeHandle.cpp in Sources */, 15217 517B5F99275EC601002DC22D /* WebPushMessageCocoa.mm in Sources */, 15184 15218 2749F6452146561E008380BF /* InjectedBundleRangeHandle.cpp in Sources */, 15185 15219 9BF5EC642541145600984E77 /* JSIPCBinding.cpp in Sources */, … … 15662 15696 targetProxy = 517B5F6C275B07C3002DC22D /* PBXContainerItemProxy */; 15663 15697 }; 15698 519797C727627B1D00BE0F83 /* PBXTargetDependency */ = { 15699 isa = PBXTargetDependency; 15700 target = E1AC2E2720F7B94C00B0897D /* Unlock Keychain */; 15701 targetProxy = 519797C627627B1D00BE0F83 /* PBXContainerItemProxy */; 15702 }; 15703 519797C927627B2300BE0F83 /* PBXTargetDependency */ = { 15704 isa = PBXTargetDependency; 15705 target = 8DC2EF4F0486A6940098B216 /* WebKit */; 15706 targetProxy = 519797C827627B2300BE0F83 /* PBXContainerItemProxy */; 15707 }; 15664 15708 5742A2E62535613F00B7BA14 /* PBXTargetDependency */ = { 15665 15709 isa = PBXTargetDependency; -
trunk/Source/WebKit/webpushd/PushClientConnection.h
r286777 r286788 57 57 const String& hostAppCodeSigningIdentifier(); 58 58 bool hostAppHasPushEntitlement(); 59 bool hostAppHasPushInjectEntitlement(); 59 60 60 61 bool debugModeIsEnabled() const { return m_debugModeEnabled; } … … 76 77 void setHostAppAuditTokenData(const Vector<uint8_t>&); 77 78 79 bool hostHasEntitlement(const char*); 80 78 81 OSObjectPtr<xpc_connection_t> m_xpcConnection; 79 82 -
trunk/Source/WebKit/webpushd/PushClientConnection.mm
r286777 r286788 90 90 bool ClientConnection::hostAppHasPushEntitlement() 91 91 { 92 if (!m_hostAppHasPushEntitlement) { 93 if (!m_hostAppAuditToken) 94 return false; 95 m_hostAppHasPushEntitlement = WTF::hasEntitlement(*m_hostAppAuditToken, "com.apple.private.webkit.webpush"); 96 } 92 if (!m_hostAppHasPushEntitlement) 93 m_hostAppHasPushEntitlement = hostHasEntitlement("com.apple.private.webkit.webpush"_s); 97 94 98 95 return *m_hostAppHasPushEntitlement; 96 } 97 98 bool ClientConnection::hostAppHasPushInjectEntitlement() 99 { 100 return hostHasEntitlement("com.apple.private.webkit.webpush.inject"_s); 101 } 102 103 bool ClientConnection::hostHasEntitlement(const char* entitlement) 104 { 105 if (!m_hostAppAuditToken) 106 return false; 107 return WTF::hasEntitlement(*m_hostAppAuditToken, entitlement); 99 108 } 100 109 … … 113 122 auto signingIdentifer = hostAppCodeSigningIdentifier(); 114 123 if (signingIdentifer.isEmpty()) 115 messageIdentifier = makeString ("[(0x", hex(reinterpret_cast<uint64_t>(m_xpcConnection.get()), WTF::HexConversionMode::Lowercase), ")] ");124 messageIdentifier = makeString("[(0x", hex(reinterpret_cast<uint64_t>(m_xpcConnection.get()), WTF::HexConversionMode::Lowercase), ")] "); 116 125 else 117 messageIdentifier = makeString ("[", signingIdentifer, " (0x", hex(reinterpret_cast<uint64_t>(m_xpcConnection.get()), WTF::HexConversionMode::Lowercase), ")] ");126 messageIdentifier = makeString("[", signingIdentifer, " (0x", hex(reinterpret_cast<uint64_t>(m_xpcConnection.get()), WTF::HexConversionMode::Lowercase), ")] "); 118 127 119 128 Daemon::singleton().broadcastDebugMessage(JSC::MessageLevel::Info, makeString(messageIdentifier, message)); -
trunk/Source/WebKit/webpushd/WebPushDaemon.h
r286777 r286788 27 27 28 28 #include "PushClientConnection.h" 29 #include "PushMessageForTesting.h" 29 30 #include "WebPushDaemonConnectionConfiguration.h" 30 31 #include "WebPushDaemonConstants.h" 32 #include "WebPushMessage.h" 33 #include <wtf/Deque.h> 31 34 #include <wtf/Forward.h> 32 35 #include <wtf/HashMap.h> … … 41 44 } 42 45 46 using WebKit::WebPushD::PushMessageForTesting; 43 47 using WebKit::WebPushD::WebPushDaemonConnectionConfiguration; 44 48 … … 63 67 void setDebugModeIsEnabled(ClientConnection*, bool); 64 68 void updateConnectionConfiguration(ClientConnection*, const WebPushDaemonConnectionConfiguration&); 69 void injectPushMessageForTesting(ClientConnection*, const PushMessageForTesting&, CompletionHandler<void(bool)>&&); 70 void getPendingPushMessages(ClientConnection*, CompletionHandler<void(const Vector<WebKit::WebPushMessage>&)>&& replySender); 65 71 66 72 void broadcastDebugMessage(JSC::MessageLevel, const String&); … … 76 82 ClientConnection* toClientConnection(xpc_connection_t); 77 83 HashMap<xpc_connection_t, Ref<ClientConnection>> m_connectionMap; 84 85 HashMap<String, Deque<PushMessageForTesting>> m_testingPushMessages; 78 86 }; 79 87 -
trunk/Source/WebKit/webpushd/WebPushDaemon.mm
r286777 r286788 71 71 END 72 72 73 FUNCTION(getPendingPushMessages) 74 ARGUMENTS() 75 REPLY(const Vector<WebKit::WebPushMessage>&) 76 END 77 73 78 FUNCTION(setDebugModeIsEnabled) 74 79 ARGUMENTS(bool) … … 77 82 FUNCTION(updateConnectionConfiguration) 78 83 ARGUMENTS(WebPushDaemonConnectionConfiguration) 84 END 85 86 FUNCTION(injectPushMessageForTesting) 87 ARGUMENTS(PushMessageForTesting) 88 REPLY(bool) 79 89 END 80 90 … … 106 116 107 117 WebPushD::EncodedMessage requestSystemNotificationPermission::encodeReply(bool reply) 118 { 119 WebKit::Daemon::Encoder encoder; 120 encoder << reply; 121 return encoder.takeBuffer(); 122 } 123 124 WebPushD::EncodedMessage injectPushMessageForTesting::encodeReply(bool reply) 125 { 126 WebKit::Daemon::Encoder encoder; 127 encoder << reply; 128 return encoder.takeBuffer(); 129 } 130 131 WebPushD::EncodedMessage getPendingPushMessages::encodeReply(const Vector<WebKit::WebPushMessage>& reply) 108 132 { 109 133 WebKit::Daemon::Encoder encoder; … … 234 258 handleWebPushDMessage<MessageInfo::updateConnectionConfiguration>(clientConnection, encodedMessage); 235 259 break; 260 case MessageType::InjectPushMessageForTesting: 261 handleWebPushDMessageWithReply<MessageInfo::injectPushMessageForTesting>(clientConnection, encodedMessage, WTFMove(replySender)); 262 break; 263 case MessageType::GetPendingPushMessages: 264 handleWebPushDMessageWithReply<MessageInfo::getPendingPushMessages>(clientConnection, encodedMessage, WTFMove(replySender)); 265 break; 236 266 } 237 267 } … … 298 328 } 299 329 330 void Daemon::injectPushMessageForTesting(ClientConnection* connection, const PushMessageForTesting& message, CompletionHandler<void(bool)>&& replySender) 331 { 332 if (!connection->hostAppHasPushInjectEntitlement()) { 333 connection->broadcastDebugMessage("Attempting to inject a push message from an unentitled process"); 334 replySender(false); 335 return; 336 } 337 338 if (message.targetAppCodeSigningIdentifier.isEmpty() || !message.registrationURL.isValid()) { 339 connection->broadcastDebugMessage("Attempting to inject an invalid push message"); 340 replySender(false); 341 return; 342 } 343 344 connection->broadcastDebugMessage(makeString("Injected a test push messasge for ", message.targetAppCodeSigningIdentifier, " at ", message.registrationURL.string())); 345 connection->broadcastDebugMessage(message.message); 346 347 auto addResult = m_testingPushMessages.ensure(message.targetAppCodeSigningIdentifier, [] { 348 return Deque<PushMessageForTesting> { }; 349 }); 350 addResult.iterator->value.append(message); 351 352 replySender(true); 353 } 354 355 void Daemon::getPendingPushMessages(ClientConnection* connection, CompletionHandler<void(const Vector<WebKit::WebPushMessage>&)>&& replySender) 356 { 357 auto hostAppCodeSigningIdentifier = connection->hostAppCodeSigningIdentifier(); 358 if (hostAppCodeSigningIdentifier.isEmpty()) { 359 replySender({ }); 360 return; 361 } 362 363 Vector<WebKit::WebPushMessage> resultMessages; 364 365 auto iterator = m_testingPushMessages.find(hostAppCodeSigningIdentifier); 366 if (iterator != m_testingPushMessages.end()) { 367 for (auto& message : iterator->value) { 368 auto data = message.message.utf8(); 369 resultMessages.append(WebKit::WebPushMessage { Vector<uint8_t> { reinterpret_cast<const uint8_t*>(data.data()), data.length() }, message.registrationURL }); 370 } 371 m_testingPushMessages.remove(iterator); 372 } 373 374 connection->broadcastDebugMessage(makeString("Fetching ", String::number(resultMessages.size()), " pending push messages")); 375 376 replySender(WTFMove(resultMessages)); 377 } 378 300 379 ClientConnection* Daemon::toClientConnection(xpc_connection_t connection) 301 380 { -
trunk/Source/WebKit/webpushd/webpushtool/WebPushToolConnection.h
r286777 r286788 26 26 #pragma once 27 27 28 #include "PushMessageForTesting.h" 28 29 #include <memory> 29 30 #include <wtf/RetainPtr.h> 31 #include <wtf/URL.h> 30 32 #include <wtf/WeakPtr.h> 31 33 #include <wtf/spi/darwin/XPCSPI.h> 34 35 using WebKit::WebPushD::PushMessageForTesting; 32 36 33 37 namespace WebPushTool { … … 55 59 void connectToService(); 56 60 61 void setPushMessage(std::unique_ptr<PushMessageForTesting>&& message) { m_pushMessage = WTFMove(message); } 62 57 63 private: 58 64 void messageReceived(xpc_object_t); … … 61 67 void startAction(); 62 68 void startDebugStreamAction(); 69 void sendPushMessage(); 63 70 64 71 void sendAuditToken(); 65 72 66 73 Action m_action; 67 74 bool m_reconnect { false }; 68 75 RetainPtr<xpc_connection_t> m_connection; 69 76 const char* m_serviceName; 77 78 std::unique_ptr<PushMessageForTesting> m_pushMessage; 70 79 }; 71 80 -
trunk/Source/WebKit/webpushd/webpushtool/WebPushToolConnection.mm
r286777 r286788 27 27 #import "WebPushToolConnection.h" 28 28 29 #import "DaemonEncoder.h" 30 #import "DaemonUtilities.h" 31 #import "WebPushDaemonConstants.h" 29 32 #import <mach/mach_init.h> 30 33 #import <mach/task.h> … … 119 122 break; 120 123 }; 124 125 if (m_pushMessage) 126 sendPushMessage(); 127 } 128 129 void Connection::sendPushMessage() 130 { 131 ASSERT(m_pushMessage); 132 133 WebKit::Daemon::Encoder encoder; 134 encoder << *m_pushMessage; 135 136 auto dictionary = adoptNS(xpc_dictionary_create(nullptr, nullptr, 0)); 137 xpc_dictionary_set_uint64(dictionary.get(), WebKit::WebPushD::protocolVersionKey, WebKit::WebPushD::protocolVersionValue); 138 xpc_dictionary_set_value(dictionary.get(), WebKit::WebPushD::protocolEncodedMessageKey, WebKit::vectorToXPCData(encoder.takeBuffer()).get()); 139 xpc_dictionary_set_uint64(dictionary.get(), WebKit::WebPushD::protocolMessageTypeKey, static_cast<uint64_t>(WebKit::WebPushD::MessageType::InjectPushMessageForTesting)); 140 141 xpc_connection_send_message_with_reply(m_connection.get(), dictionary.get(), dispatch_get_main_queue(), ^(xpc_object_t resultMessage) { 142 // This reply handler intentionally left blank 143 }); 121 144 } 122 145 -
trunk/Source/WebKit/webpushd/webpushtool/WebPushToolMain.mm
r286777 r286788 25 25 26 26 #import "config.h" 27 #import "PushMessageForTesting.h" 27 28 #import "WebPushToolConnection.h" 28 29 #import <Foundation/Foundation.h> 29 30 #import <optional> 30 31 #import <wtf/MainThread.h> 32 33 using WebKit::WebPushD::PushMessageForTesting; 31 34 32 35 __attribute__((__noreturn__)) … … 37 40 fprintf(stderr, "Usage: webpushtool [options]\n"); 38 41 fprintf(stderr, "\n"); 39 fprintf(stderr, " --development Connects to mach service \"org.webkit.webpushtestdaemon.service\" (Default)\n"); 40 fprintf(stderr, " --production Connects to mach service \"com.apple.webkit.webpushd.service\"\n"); 41 fprintf(stderr, " --streamDebugMessages Stream debug messages from webpushd\n"); 42 fprintf(stderr, " --reconnect Reconnect after connection is lost\n"); 42 fprintf(stderr, " --development\n"); 43 fprintf(stderr, " Connects to mach service \"org.webkit.webpushtestdaemon.service\" (Default)\n"); 44 fprintf(stderr, " --production\n"); 45 fprintf(stderr, " Connects to mach service \"com.apple.webkit.webpushd.service\"\n"); 46 fprintf(stderr, " --streamDebugMessages\n"); 47 fprintf(stderr, " Stream debug messages from webpushd\n"); 48 fprintf(stderr, " --reconnect\n"); 49 fprintf(stderr, " Reconnect after connection is lost\n"); 50 fprintf(stderr, " --push <target app identifier> <registration URL> <message>\n"); 51 fprintf(stderr, " Inject a test push messasge to the target app and registration URL\n"); 43 52 fprintf(stderr, "\n"); 44 53 45 54 exit(-1); 55 } 56 57 static std::unique_ptr<PushMessageForTesting> pushMessageFromArguments(NSEnumerator<NSString *> *enumerator) 58 { 59 NSString *appIdentifier = [enumerator nextObject]; 60 if (!appIdentifier) 61 return nullptr; 62 63 NSString *registrationString = [enumerator nextObject]; 64 if (!registrationString) 65 return nullptr; 66 67 NSURL *registrationURL = [NSURL URLWithString:registrationString]; 68 if (!registrationURL) 69 return nullptr; 70 71 NSString *message = [enumerator nextObject]; 72 if (!message) 73 return nullptr; 74 75 PushMessageForTesting pushMessage = { appIdentifier, registrationURL, message }; 76 return makeUniqueWithoutFastMallocCheck<PushMessageForTesting>(WTFMove(pushMessage)); 46 77 } 47 78 … … 53 84 auto reconnect = WebPushTool::Reconnect::No; 54 85 std::optional<WebPushTool::Action> action; 86 std::unique_ptr<PushMessageForTesting> pushMessage; 55 87 56 88 @autoreleasepool { … … 59 91 printUsageAndTerminate(@"No arguments provided"); 60 92 61 for (NSString *argument in [arguments subarrayWithRange:NSMakeRange(1, arguments.count - 1)]) { 93 NSEnumerator<NSString *> *enumerator = [[arguments subarrayWithRange:NSMakeRange(1, arguments.count - 1)] objectEnumerator]; 94 NSString *argument = [enumerator nextObject]; 95 while (argument) { 62 96 if ([argument isEqualToString:@"--production"]) 63 97 preferTestService = WebPushTool::PreferTestService::No; … … 68 102 else if ([argument isEqualToString:@"--reconnect"]) 69 103 reconnect = WebPushTool::Reconnect::Yes; 70 else 104 else if ([argument isEqualToString:@"--push"]) { 105 pushMessage = pushMessageFromArguments(enumerator); 106 if (!pushMessage) 107 printUsageAndTerminate([NSString stringWithFormat:@"Invalid push arguments specified"]); 108 } else 71 109 printUsageAndTerminate([NSString stringWithFormat:@"Invalid option provided: %@", argument]); 110 111 argument = [enumerator nextObject]; 72 112 } 73 113 } 74 114 75 if (!action )115 if (!action && !pushMessage) 76 116 printUsageAndTerminate(@"No action provided"); 77 117 78 118 auto connection = WebPushTool::Connection::create(*action, preferTestService, reconnect); 119 if (pushMessage) 120 connection->setPushMessage(WTFMove(pushMessage)); 121 79 122 connection->connectToService(); 80 123 -
trunk/Tools/ChangeLog
r286785 r286788 1 2021-12-09 Brady Eidson <beidson@apple.com> 2 3 Add ability to inject messages into webpushd 4 https://bugs.webkit.org/show_bug.cgi?id=233988 5 6 Reviewed by Alex Christensen. 7 8 * TestWebKitAPI/Configurations/TestWebKitAPI-iOS.entitlements: 9 * TestWebKitAPI/Configurations/TestWebKitAPI-macOS-internal.entitlements: 10 * TestWebKitAPI/Configurations/TestWebKitAPI-macOS.entitlements: 11 12 * TestWebKitAPI/Tests/WebKitCocoa/PushAPI.mm: 13 (messageDictionary): 14 15 * TestWebKitAPI/Tests/WebKitCocoa/WebPushDaemon.mm: 16 (TestWebKitAPI::createMessageDictionary): 17 (TestWebKitAPI::sendMessageToDaemon): 18 (TestWebKitAPI::sendMessageToDaemonWaitingForReply): 19 (TestWebKitAPI::sendConfigurationWithAuditToken): 20 (TestWebKitAPI::createAndConfigureConnectionToService): 21 (TestWebKitAPI::encodeString): 22 (TestWebKitAPI::TEST): 23 (TestWebKitAPI::function): 24 * TestWebKitAPI/cocoa/TestWKWebView.h: 25 1 26 2021-12-09 J Pascoe <j_pascoe@apple.com> 2 27 -
trunk/Tools/TestWebKitAPI/Configurations/TestWebKitAPI-iOS.entitlements
r286777 r286788 13 13 <key>com.apple.private.webkit.webpush</key> 14 14 <true/> 15 <key>com.apple.private.webkit.webpush.inject</key> 16 <true/> 15 17 <key>com.apple.private.xpc.launchd.job-manager</key> 16 18 <string>TestWebKitAPI</string> -
trunk/Tools/TestWebKitAPI/Configurations/TestWebKitAPI-macOS-internal.entitlements
r286777 r286788 6 6 <string>TestWebKitAPI</string> 7 7 <key>com.apple.private.webkit.webpush</key> 8 <true/> 9 <key>com.apple.private.webkit.webpush.inject</key> 8 10 <true/> 9 11 <key>com.apple.hid.manager.user-access-device</key> -
trunk/Tools/TestWebKitAPI/Configurations/TestWebKitAPI-macOS.entitlements
r286777 r286788 11 11 <key>com.apple.private.webkit.webpush</key> 12 12 <true/> 13 <key>com.apple.private.webkit.webpush.inject</key> 14 <true/> 13 15 <key>com.apple.security.temporary-exception.sbpl</key> 14 16 <array> -
trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/PushAPI.mm
r286777 r286788 35 35 #import <WebKit/_WKWebsiteDataStoreConfiguration.h> 36 36 37 static NSDictionary *messageDictionary(NSData *data, NSURL *registration) 38 { 39 return @{ 40 @"WebKitPushData" : data, 41 @"WebKitPushRegistrationURL" : registration 42 }; 43 } 44 37 45 static String expectedMessage; 38 46 … … 147 155 NSString *message = @"Sweet Potatoes"; 148 156 expectedMessage = "Received: Sweet Potatoes"; 149 [[configuration websiteDataStore] _processPushMessage:[message dataUsingEncoding:NSUTF8StringEncoding] registration:[server.request() URL] completionHandler:^(bool result) { 157 158 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) { 150 159 pushMessageSuccessful = result; 151 160 pushMessageProcessed = true; … … 161 170 message = @"Rotten Potatoes"; 162 171 expectedMessage = "Received: Rotten Potatoes"; 163 [[configuration websiteDataStore] _processPushMessage: [message dataUsingEncoding:NSUTF8StringEncoding] registration:[server.request() URL]completionHandler:^(bool result) {172 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) { 164 173 pushMessageSuccessful = result; 165 174 pushMessageProcessed = true; … … 235 244 pushMessageSuccessful = false; 236 245 NSString *message = @"Sweet Potatoes"; 237 [[configuration websiteDataStore] _processPushMessage: [message dataUsingEncoding:NSUTF8StringEncoding] registration:[server.request() URL]completionHandler:^(bool result) {246 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) { 238 247 pushMessageSuccessful = result; 239 248 pushMessageProcessed = true; … … 280 289 pushMessageSuccessful = false; 281 290 NSString *message = @"Rotten Potatoes"; 282 [[configuration websiteDataStore] _processPushMessage: [message dataUsingEncoding:NSUTF8StringEncoding] registration:[server.request() URL]completionHandler:^(bool result) {291 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) { 283 292 pushMessageSuccessful = result; 284 293 pushMessageProcessed = true; … … 333 342 pushMessageSuccessful = false; 334 343 NSString *message = @"Timeless Potatoes"; 335 [[configuration websiteDataStore] _processPushMessage: [message dataUsingEncoding:NSUTF8StringEncoding] registration:[server.request() URL]completionHandler:^(bool result) {344 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) { 336 345 pushMessageSuccessful = result; 337 346 pushMessageProcessed = true; -
trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebPushDaemon.mm
r286777 r286788 27 27 28 28 #import "DaemonTestUtilities.h" 29 #import "HTTPServer.h" 29 30 #import "Test.h" 30 31 #import "TestURLSchemeHandler.h" … … 36 37 #import <WebKit/_WKExperimentalFeature.h> 37 38 #import <WebKit/_WKWebsiteDataStoreConfiguration.h> 38 #import <array>39 39 #import <mach/mach_init.h> 40 40 #import <mach/task.h> … … 163 163 } 164 164 165 static RetainPtr<xpc_object_t> createMessageDictionary(uint8_t messageType, const Vector<uint8_t>& message) 166 { 167 auto dictionary = adoptNS(xpc_dictionary_create(nullptr, nullptr, 0)); 168 xpc_dictionary_set_uint64(dictionary.get(), "protocol version", 1); 169 xpc_dictionary_set_uint64(dictionary.get(), "message type", messageType); 170 xpc_dictionary_set_data(dictionary.get(), "encoded message", message.data(), message.size()); 171 return WTFMove(dictionary); 172 } 173 174 // Uses an existing connection to the daemon for a one-off message 175 void sendMessageToDaemon(xpc_connection_t connection, uint8_t messageType, const Vector<uint8_t>& message) 176 { 177 auto dictionary = createMessageDictionary(messageType, message); 178 xpc_connection_send_message(connection, dictionary.get()); 179 } 180 181 // Uses an existing connection to the daemon for a one-off message, waiting for the reply 182 void sendMessageToDaemonWaitingForReply(xpc_connection_t connection, uint8_t messageType, const Vector<uint8_t>& message) 183 { 184 auto dictionary = createMessageDictionary(messageType, message); 185 186 __block bool done = false; 187 xpc_connection_send_message_with_reply(connection, dictionary.get(), dispatch_get_main_queue(), ^(xpc_object_t request) { 188 done = true; 189 }); 190 191 TestWebKitAPI::Util::run(&done); 192 } 193 194 static void sendConfigurationWithAuditToken(xpc_connection_t connection) 195 { 196 audit_token_t token = { 0, 0, 0, 0, 0, 0, 0, 0 }; 197 mach_msg_type_number_t auditTokenCount = TASK_AUDIT_TOKEN_COUNT; 198 kern_return_t result = task_info(mach_task_self(), TASK_AUDIT_TOKEN, (task_info_t)(&token), &auditTokenCount); 199 if (result != KERN_SUCCESS) { 200 EXPECT_TRUE(false); 201 return; 202 } 203 204 // Send configuration with audit token 205 { 206 Vector<uint8_t> encodedMessage(42); 207 encodedMessage.fill(0); 208 encodedMessage[1] = 1; 209 encodedMessage[2] = 32; 210 memcpy(&encodedMessage[10], &token, sizeof(token)); 211 sendMessageToDaemon(connection, 6, encodedMessage); 212 } 213 } 214 215 RetainPtr<xpc_connection_t> createAndConfigureConnectionToService(const char* serviceName) 216 { 217 auto connection = adoptNS(xpc_connection_create_mach_service(serviceName, dispatch_get_main_queue(), 0)); 218 xpc_connection_set_event_handler(connection.get(), ^(xpc_object_t) { }); 219 xpc_connection_activate(connection.get()); 220 sendConfigurationWithAuditToken(connection.get()); 221 222 return WTFMove(connection); 223 } 224 225 static Vector<uint8_t> encodeString(const String& message) 226 { 227 ASSERT(message.is8Bit()); 228 auto utf8 = message.utf8(); 229 230 Vector<uint8_t> result(utf8.length() + 5); 231 result[0] = static_cast<uint8_t>(utf8.length()); 232 result[1] = static_cast<uint8_t>(utf8.length() >> 8); 233 result[2] = static_cast<uint8_t>(utf8.length() >> 16); 234 result[3] = static_cast<uint8_t>(utf8.length() >> 24); 235 result[4] = 0x01; 236 237 auto data = utf8.data(); 238 for (size_t i = 0; i < utf8.length(); ++i) 239 result[5 + i] = data[i]; 240 241 return result; 242 } 243 165 244 // FIXME: Re-enable this test on Mac once webkit.org/232857 is resolved. 166 245 #if PLATFORM(MAC) … … 197 276 198 277 xpc_connection_activate(connection.get()); 199 200 audit_token_t token = { 0, 0, 0, 0, 0, 0, 0, 0 }; 201 mach_msg_type_number_t auditTokenCount = TASK_AUDIT_TOKEN_COUNT; 202 kern_return_t result = task_info(mach_task_self(), TASK_AUDIT_TOKEN, (task_info_t)(&token), &auditTokenCount); 203 if (result != KERN_SUCCESS) { 204 EXPECT_TRUE(false); 205 return; 206 } 207 208 // Send configuration with audit token 209 { 210 std::array<uint8_t, 42> encodedMessage; 211 encodedMessage.fill(0); 212 encodedMessage[1] = 1; 213 encodedMessage[2] = 32; 214 memcpy(&encodedMessage[10], &token, sizeof(token)); 215 auto dictionary = adoptNS(xpc_dictionary_create(nullptr, nullptr, 0)); 216 xpc_dictionary_set_uint64(dictionary.get(), "protocol version", 1); 217 xpc_dictionary_set_uint64(dictionary.get(), "message type", 6); 218 xpc_dictionary_set_data(dictionary.get(), "encoded message", encodedMessage.data(), encodedMessage.size()); 219 xpc_connection_send_message(connection.get(), dictionary.get()); 220 } 278 sendConfigurationWithAuditToken(connection.get()); 221 279 222 280 // Enable debug messages, and wait for the resulting debug message 223 281 { 224 282 auto dictionary = adoptNS(xpc_dictionary_create(nullptr, nullptr, 0)); 225 std::array<uint8_t, 1> encodedMessage { 1 }; 226 xpc_dictionary_set_uint64(dictionary.get(), "protocol version", 1); 227 xpc_dictionary_set_uint64(dictionary.get(), "message type", 5); 228 xpc_dictionary_set_data(dictionary.get(), "encoded message", encodedMessage.data(), encodedMessage.size()); 229 230 xpc_connection_send_message(connection.get(), dictionary.get()); 283 Vector<uint8_t> encodedMessage(1); 284 encodedMessage[0] = 1; 285 sendMessageToDaemon(connection.get(), 5, encodedMessage); 231 286 TestWebKitAPI::Util::run(&done); 232 287 } … … 234 289 // Echo and wait for a reply 235 290 auto dictionary = adoptNS(xpc_dictionary_create(nullptr, nullptr, 0)); 236 std::array<uint8_t, 10> encodedString { 5, 0, 0, 0, 1, 'h', 'e', 'l', 'l', 'o' };291 auto encodedString = encodeString("hello"); 237 292 xpc_dictionary_set_uint64(dictionary.get(), "protocol version", 1); 238 293 xpc_dictionary_set_uint64(dictionary.get(), "message type", 1); … … 342 397 } 343 398 399 static const char* mainSWBytes = R"SWRESOURCE( 400 <script> 401 function log(msg) 402 { 403 window.webkit.messageHandlers.sw.postMessage(msg); 404 } 405 406 const channel = new MessageChannel(); 407 channel.port1.onmessage = (event) => log(event.data); 408 409 navigator.serviceWorker.register('/sw.js').then((registration) => { 410 if (registration.active) { 411 registration.active.postMessage({port: channel.port2}, [channel.port2]); 412 return; 413 } 414 worker = registration.installing; 415 worker.addEventListener('statechange', function() { 416 if (worker.state == 'activated') 417 worker.postMessage({port: channel.port2}, [channel.port2]); 418 }); 419 }).catch(function(error) { 420 log("Registration failed with: " + error); 421 }); 422 </script> 423 )SWRESOURCE"; 424 425 static const char* scriptBytes = R"SWRESOURCE( 426 let port; 427 self.addEventListener("message", (event) => { 428 port = event.data.port; 429 port.postMessage("Ready"); 430 }); 431 self.addEventListener("push", (event) => { 432 try { 433 if (!event.data) { 434 port.postMessage("Received: null data"); 435 return; 436 } 437 const value = event.data.text(); 438 port.postMessage("Received: " + value); 439 if (value != 'Sweet Potatoes') 440 event.waitUntil(Promise.reject('I want sweet potatoes')); 441 } catch (e) { 442 port.postMessage("Got exception " + e); 443 } 444 }); 445 )SWRESOURCE"; 446 447 static void clearWebsiteDataStore(WKWebsiteDataStore *store) 448 { 449 __block bool clearedStore = false; 450 [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() { 451 clearedStore = true; 452 }]; 453 TestWebKitAPI::Util::run(&clearedStore); 454 } 455 456 // FIXME: Re-enable this test on Mac once webkit.org/232857 is resolved. 457 #if PLATFORM(MAC) 458 TEST(WebPushD, DISABLED_HandleInjectedPush) 459 #else 460 TEST(WebPushD, HandleInjectedPush) 461 #endif 462 { 463 [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins]; 464 465 NSURL *tempDirectory = setUpTestWebPushD(); 466 467 auto dataStoreConfiguration = adoptNS([_WKWebsiteDataStoreConfiguration new]); 468 dataStoreConfiguration.get().webPushMachServiceName = @"org.webkit.webpushtestdaemon.service"; 469 dataStoreConfiguration.get().webPushDaemonUsesMockBundlesForTesting = YES; 470 auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:dataStoreConfiguration.get()]); 471 472 auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]); 473 configuration.get().websiteDataStore = dataStore.get(); 474 clearWebsiteDataStore([configuration websiteDataStore]); 475 476 [configuration.get().preferences _setNotificationsEnabled:YES]; 477 for (_WKExperimentalFeature *feature in [WKPreferences _experimentalFeatures]) { 478 if ([feature.key isEqualToString:@"BuiltInNotificationsEnabled"]) 479 [[configuration preferences] _setEnabled:YES forFeature:feature]; 480 } 481 482 auto messageHandler = adoptNS([[TestMessageHandler alloc] init]); 483 [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"]; 484 __block bool done = false; 485 [messageHandler addMessage:@"Ready" withHandler:^{ 486 done = true; 487 }]; 488 [messageHandler addMessage:@"Received: Hello World" withHandler:^{ 489 done = true; 490 }]; 491 492 TestWebKitAPI::HTTPServer server({ 493 { "/", { mainSWBytes } }, 494 { "/sw.js", { { { "Content-Type", "application/javascript" } }, scriptBytes } } 495 }, TestWebKitAPI::HTTPServer::Protocol::Http); 496 497 auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]); 498 [webView loadRequest:server.request()]; 499 500 TestWebKitAPI::Util::run(&done); 501 done = false; 502 503 // Inject push message 504 auto encodedMessage = encodeString("com.apple.WebKit.TestWebKitAPI"); 505 encodedMessage.appendVector(encodeString(server.request().URL.absoluteString)); 506 encodedMessage.appendVector(encodeString("Hello World")); 507 508 auto utilityConnection = createAndConfigureConnectionToService("org.webkit.webpushtestdaemon.service"); 509 sendMessageToDaemonWaitingForReply(utilityConnection.get(), 7, encodedMessage); 510 511 // Fetch push messages 512 __block RetainPtr<NSArray<NSDictionary *>> messages; 513 [dataStore _getPendingPushMessages:^(NSArray<NSDictionary *> *rawMessages) { 514 messages = rawMessages; 515 done = true; 516 }]; 517 TestWebKitAPI::Util::run(&done); 518 done = false; 519 520 EXPECT_EQ([messages count], 1u); 521 522 // Handle push message 523 __block bool pushMessageProcessed = false; 524 [dataStore _processPushMessage:[messages firstObject] completionHandler:^(bool result) { 525 pushMessageProcessed = true; 526 }]; 527 TestWebKitAPI::Util::run(&done); 528 TestWebKitAPI::Util::run(&pushMessageProcessed); 529 530 cleanUpTestWebPushD(tempDirectory); 531 } 532 344 533 } // namespace TestWebKitAPI 345 534 -
trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.h
r286777 r286788 79 79 @interface TestMessageHandler : NSObject <WKScriptMessageHandler> 80 80 - (void)addMessage:(NSString *)message withHandler:(dispatch_block_t)handler; 81 - (void)setWildcardMessageHandler:(void (^)(NSString *))handler; 81 82 @end 82 83
Note:
See TracChangeset
for help on using the changeset viewer.