Title: [268848] trunk
Revision
268848
Author
[email protected]
Date
2020-10-21 17:41:50 -0700 (Wed, 21 Oct 2020)

Log Message

IPC testing API should have the capability to observe messages being sent and received
https://bugs.webkit.org/show_bug.cgi?id=217870

Reviewed by Darin Adler.

Source/WebKit:

Added IPC.addIncomingMessageListener and IPC.addOutgoingMessageListener which allows _javascript_
to observe IPC messages being sent or received by WebContent process. We use the generated code
added in r268503 to decode the IPC arguments.

Tests: TestWebKitAPI.IPCTestingAPI.CanInterceptAlert
       TestWebKitAPI.IPCTestingAPI.CanInterceptHasStorageAccess
       TestWebKitAPI.IPCTestingAPI.CanInterceptFindString

* Platform/IPC/Connection.cpp:
(IPC::Connection::sendMessage): Added the code to invoke MessageObserver::willSendMessage.
Also remove any stale MessageObserver as neded.
(IPC::Connection::addMessageObserver): Added.
(IPC::Connection::dispatchMessage): Added the code to invoke MessageObserver::didReceiveMessage.
Also remove any stale MessageObserver as neded.
* Platform/IPC/Connection.h:
(IPC::Connection::MessageObserver): Added. A pure virtual interface for observing IPC messages.
* Platform/IPC/JSIPCBinding.h:
(IPC::jsValueForDecodedStringArgumentValue): Extracted from jsValueForDecodedArgumentValue<String>.
Now takes the type name as an argument.
(IPC::jsValueForDecodedArgumentValue<URL>): Use "URL" as the type name.
(IPC::jsValueForDecodedArgumentValue<RegistrableDomain>): Use "RegistrableDomain" as the type name.
(IPC::jsValueForDecodedArgumentValue<OptionSet<U>>): Added. Specializations for OptionSet<U>
* WebProcess/WebPage/IPCTestingAPI.cpp:
(WebKit::IPCTestingAPI::JSMessageListener): Added. Implements IPC::MessageObserver.
(WebKit::IPCTestingAPI::JSIPC::staticFunctions):
(WebKit::IPCTestingAPI::createTypeError): Moved.
(WebKit::IPCTestingAPI::JSIPC::addMessageListener): Added.
(WebKit::IPCTestingAPI::JSIPC::addIncomingMessageListener): Added.
(WebKit::IPCTestingAPI::JSIPC::addOutgoingMessageListener): Added.
(WebKit::IPCTestingAPI::JSMessageListener::JSMessageListener): Added.
(WebKit::IPCTestingAPI::JSMessageListener::didReceiveMessage): Added.
(WebKit::IPCTestingAPI::JSMessageListener::willSendMessage): Added.
(WebKit::IPCTestingAPI::JSMessageListener::jsDescriptionFromDecoder): Added.

Tools:

Added tests to intercept IPC messages sent and received by WebContent process.

* TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm:
(IPCTestingAPI.CanInterceptAlert):
(IPCTestingAPI.CanInterceptHasStorageAccess):
(IPCTestingAPI.CanInterceptFindString):

Modified Paths

Diff

Modified: trunk/Source/WebKit/ChangeLog (268847 => 268848)


--- trunk/Source/WebKit/ChangeLog	2020-10-22 00:40:55 UTC (rev 268847)
+++ trunk/Source/WebKit/ChangeLog	2020-10-22 00:41:50 UTC (rev 268848)
@@ -1,3 +1,44 @@
+2020-10-21  Ryosuke Niwa  <[email protected]>
+
+        IPC testing API should have the capability to observe messages being sent and received
+        https://bugs.webkit.org/show_bug.cgi?id=217870
+
+        Reviewed by Darin Adler.
+
+        Added IPC.addIncomingMessageListener and IPC.addOutgoingMessageListener which allows _javascript_
+        to observe IPC messages being sent or received by WebContent process. We use the generated code
+        added in r268503 to decode the IPC arguments.
+
+        Tests: TestWebKitAPI.IPCTestingAPI.CanInterceptAlert
+               TestWebKitAPI.IPCTestingAPI.CanInterceptHasStorageAccess
+               TestWebKitAPI.IPCTestingAPI.CanInterceptFindString
+
+        * Platform/IPC/Connection.cpp:
+        (IPC::Connection::sendMessage): Added the code to invoke MessageObserver::willSendMessage.
+        Also remove any stale MessageObserver as neded.
+        (IPC::Connection::addMessageObserver): Added.
+        (IPC::Connection::dispatchMessage): Added the code to invoke MessageObserver::didReceiveMessage.
+        Also remove any stale MessageObserver as neded.
+        * Platform/IPC/Connection.h:
+        (IPC::Connection::MessageObserver): Added. A pure virtual interface for observing IPC messages.
+        * Platform/IPC/JSIPCBinding.h:
+        (IPC::jsValueForDecodedStringArgumentValue): Extracted from jsValueForDecodedArgumentValue<String>.
+        Now takes the type name as an argument.
+        (IPC::jsValueForDecodedArgumentValue<URL>): Use "URL" as the type name.
+        (IPC::jsValueForDecodedArgumentValue<RegistrableDomain>): Use "RegistrableDomain" as the type name.
+        (IPC::jsValueForDecodedArgumentValue<OptionSet<U>>): Added. Specializations for OptionSet<U>
+        * WebProcess/WebPage/IPCTestingAPI.cpp:
+        (WebKit::IPCTestingAPI::JSMessageListener): Added. Implements IPC::MessageObserver.
+        (WebKit::IPCTestingAPI::JSIPC::staticFunctions):
+        (WebKit::IPCTestingAPI::createTypeError): Moved.
+        (WebKit::IPCTestingAPI::JSIPC::addMessageListener): Added.
+        (WebKit::IPCTestingAPI::JSIPC::addIncomingMessageListener): Added.
+        (WebKit::IPCTestingAPI::JSIPC::addOutgoingMessageListener): Added.
+        (WebKit::IPCTestingAPI::JSMessageListener::JSMessageListener): Added.
+        (WebKit::IPCTestingAPI::JSMessageListener::didReceiveMessage): Added.
+        (WebKit::IPCTestingAPI::JSMessageListener::willSendMessage): Added.
+        (WebKit::IPCTestingAPI::JSMessageListener::jsDescriptionFromDecoder): Added.
+
 2020-10-21  Aditya Keerthi  <[email protected]>
 
         Remove unused UIDocumentMenuViewController SPI declarations

Modified: trunk/Source/WebKit/Platform/IPC/Connection.cpp (268847 => 268848)


--- trunk/Source/WebKit/Platform/IPC/Connection.cpp	2020-10-22 00:40:55 UTC (rev 268847)
+++ trunk/Source/WebKit/Platform/IPC/Connection.cpp	2020-10-22 00:41:50 UTC (rev 268848)
@@ -453,6 +453,20 @@
     if (!isValid())
         return false;
 
+#if ENABLE(IPC_TESTING_API)
+    if (isMainThread()) {
+        bool hasDeadObservers = false;
+        for (auto& observerWeakPtr : m_messageObservers) {
+            if (auto* observer = observerWeakPtr.get())
+                observer->willSendMessage(*encoder, sendOptions);
+            else
+                hasDeadObservers = true;
+        }
+        if (hasDeadObservers)
+            m_messageObservers.removeAllMatching([](auto& observer) { return !observer; });
+    }
+#endif
+
     if (isMainThread() && m_inDispatchMessageMarkedToUseFullySynchronousModeForTesting && !encoder->isSyncMessage() && !(encoder->messageReceiverName() == ReceiverName::IPC) && !sendOptions.contains(SendOption::IgnoreFullySynchronousMode)) {
         uint64_t syncRequestID;
         auto wrappedMessage = createSyncMessageEncoder(MessageName::WrappedAsyncMessageForTesting, encoder->destinationID(), syncRequestID);
@@ -468,6 +482,9 @@
     else if (sendOptions.contains(SendOption::DispatchMessageEvenWhenWaitingForUnboundedSyncReply))
         encoder->setShouldDispatchMessageWhenWaitingForSyncReply(ShouldDispatchWhenWaitingForSyncReply::YesDuringUnboundedIPC);
 
+#if ENABLE(IPC_TESTING_API)
+#endif
+
     {
         auto locker = holdLock(m_outgoingMessagesMutex);
         m_outgoingMessages.append(WTFMove(encoder));
@@ -822,6 +839,13 @@
     m_incomingMessagesThrottler = makeUnique<MessagesThrottler>(*this, &Connection::dispatchIncomingMessages);
 }
 
+#if ENABLE(IPC_TESTING_API)
+void Connection::addMessageObserver(const MessageObserver& observer)
+{
+    m_messageObservers.append(makeWeakPtr(observer));
+}
+#endif
+
 void Connection::postConnectionDidCloseOnConnectionWorkQueue()
 {
     m_connectionQueue->dispatch([protectedThis = makeRef(*this)]() mutable {
@@ -1007,6 +1031,20 @@
         return;
     }
 
+#if ENABLE(IPC_TESTING_API)
+    if (isMainThread()) {
+        bool hasDeadObservers = false;
+        for (auto& observerWeakPtr : m_messageObservers) {
+            if (auto* observer = observerWeakPtr.get())
+                observer->didReceiveMessage(decoder);
+            else
+                hasDeadObservers = true;
+        }
+        if (hasDeadObservers)
+            m_messageObservers.removeAllMatching([](auto& observer) { return !observer; });
+    }
+#endif
+
     m_client.didReceiveMessage(*this, decoder);
 }
 

Modified: trunk/Source/WebKit/Platform/IPC/Connection.h (268847 => 268848)


--- trunk/Source/WebKit/Platform/IPC/Connection.h	2020-10-22 00:40:55 UTC (rev 268847)
+++ trunk/Source/WebKit/Platform/IPC/Connection.h	2020-10-22 00:41:50 UTC (rev 268848)
@@ -139,6 +139,15 @@
         void derefMessageReceiver() final { ThreadSafeRefCounted::deref(); }
     };
 
+#if ENABLE(IPC_TESTING_API)
+    class MessageObserver : public CanMakeWeakPtr<MessageObserver> {
+    public:
+        virtual ~MessageObserver() = default;
+        virtual void willSendMessage(const Encoder&, OptionSet<SendOption>) = 0;
+        virtual void didReceiveMessage(const Decoder&) = 0;
+    };
+#endif
+
 #if USE(UNIX_DOMAIN_SOCKETS)
     typedef int Identifier;
     static bool identifierIsValid(Identifier identifier) { return identifier != -1; }
@@ -288,6 +297,8 @@
     void enableIncomingMessagesThrottling();
 
 #if ENABLE(IPC_TESTING_API)
+    void addMessageObserver(const MessageObserver&);
+
     void setIgnoreInvalidMessageForTesting() { m_ignoreInvalidMessageForTesting = true; }
     bool ignoreInvalidMessageForTesting() const { return m_ignoreInvalidMessageForTesting; }
 #endif
@@ -418,6 +429,7 @@
     uint64_t m_nextIncomingSyncMessageCallbackID { 0 };
 
 #if ENABLE(IPC_TESTING_API)
+    Vector<WeakPtr<MessageObserver>> m_messageObservers;
     bool m_ignoreInvalidMessageForTesting { false };
 #endif
 

Modified: trunk/Source/WebKit/Platform/IPC/JSIPCBinding.h (268847 => 268848)


--- trunk/Source/WebKit/Platform/IPC/JSIPCBinding.h	2020-10-22 00:40:55 UTC (rev 268847)
+++ trunk/Source/WebKit/Platform/IPC/JSIPCBinding.h	2020-10-22 00:41:50 UTC (rev 268848)
@@ -38,6 +38,8 @@
 #include <wtf/URL.h>
 #include <wtf/text/WTFString.h>
 
+namespace IPC {
+
 template<typename T, std::enable_if_t<!std::is_arithmetic<T>::value && !std::is_enum<T>::value>* = nullptr>
 JSC::JSValue jsValueForDecodedArgumentValue(JSC::JSGlobalObject*, const T&)
 {
@@ -44,30 +46,35 @@
     return JSC::jsUndefined();
 }
 
-template<>
-JSC::JSValue jsValueForDecodedArgumentValue(JSC::JSGlobalObject* globalObject, const String& value)
+inline JSC::JSValue jsValueForDecodedStringArgumentValue(JSC::JSGlobalObject* globalObject, const String& value, ASCIILiteral type)
 {
     auto& vm = globalObject->vm();
     auto scope = DECLARE_THROW_SCOPE(vm);
     auto* object = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype());
     RETURN_IF_EXCEPTION(scope, JSC::JSValue());
-    object->putDirect(vm, JSC::Identifier::fromString(vm, "type"_s), JSC::jsNontrivialString(vm, "String"_s));
+    object->putDirect(vm, JSC::Identifier::fromString(vm, "type"_s), JSC::jsNontrivialString(vm, type));
     RETURN_IF_EXCEPTION(scope, JSC::JSValue());
-    object->putDirect(vm, JSC::Identifier::fromString(vm, "value"_s), JSC::jsNontrivialString(vm, value));
+    object->putDirect(vm, JSC::Identifier::fromString(vm, "value"_s), value.isNull() ? JSC::jsNull() : JSC::jsString(vm, value));
     RETURN_IF_EXCEPTION(scope, JSC::JSValue());
     return object;
 }
 
 template<>
+JSC::JSValue jsValueForDecodedArgumentValue(JSC::JSGlobalObject* globalObject, const String& value)
+{
+    return jsValueForDecodedStringArgumentValue(globalObject, value, "String"_s);
+}
+
+template<>
 JSC::JSValue jsValueForDecodedArgumentValue(JSC::JSGlobalObject* globalObject, const URL& value)
 {
-    return jsValueForDecodedArgumentValue(globalObject, value.string());
+    return jsValueForDecodedStringArgumentValue(globalObject, value.string(), "URL"_s);
 }
 
 template<>
 JSC::JSValue jsValueForDecodedArgumentValue(JSC::JSGlobalObject* globalObject, const WebCore::RegistrableDomain& value)
 {
-    return jsValueForDecodedArgumentValue(globalObject, value.string());
+    return jsValueForDecodedStringArgumentValue(globalObject, value.string(), "RegistrableDomain"_s);
 }
 
 template<typename T, std::enable_if_t<std::is_arithmetic<T>::value>* = nullptr>
@@ -207,6 +214,18 @@
     return jsValueForDecodedArgumentRect(globalObject, value, "FloatRect");
 }
 
+template<typename U>
+JSC::JSValue jsValueForDecodedArgumentValue(JSC::JSGlobalObject* globalObject, const OptionSet<U>& value)
+{    
+    auto& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+    auto result = jsValueForDecodedArgumentValue(globalObject, value.toRaw());
+    RETURN_IF_EXCEPTION(scope, JSC::JSValue());
+    result.getObject()->putDirect(vm, JSC::Identifier::fromString(vm, "isOptionSet"_s), JSC::jsBoolean(true));
+    RETURN_IF_EXCEPTION(scope, JSC::JSValue());
+    return result;
+}
+
 template<size_t remainingSize, typename... Elements>
 struct DecodedArgumentJSValueConverter {
     static bool convert(JSC::JSGlobalObject* globalObject, JSC::JSArray* array, const std::tuple<Elements...>& tuple)
@@ -253,3 +272,5 @@
         return WTF::nullopt;
     return jsValueForArgumentTuple(globalObject, *arguments);
 }
+
+}

Modified: trunk/Source/WebKit/WebProcess/WebPage/IPCTestingAPI.cpp (268847 => 268848)


--- trunk/Source/WebKit/WebProcess/WebPage/IPCTestingAPI.cpp	2020-10-22 00:40:55 UTC (rev 268847)
+++ trunk/Source/WebKit/WebProcess/WebPage/IPCTestingAPI.cpp	2020-10-22 00:41:50 UTC (rev 268848)
@@ -54,8 +54,28 @@
 
 namespace IPCTestingAPI {
 
-class JSIPC : public RefCounted<JSIPC> {
+class JSIPC;
+
+class JSMessageListener final : public IPC::Connection::MessageObserver {
+    WTF_MAKE_FAST_ALLOCATED;
 public:
+    enum class Type { Incoming, Outgoing };
+
+    JSMessageListener(JSIPC&, Type, JSContextRef, JSObjectRef callback);
+
+private:
+    void willSendMessage(const IPC::Encoder&, OptionSet<IPC::SendOption>) override;
+    void didReceiveMessage(const IPC::Decoder&) override;
+    JSC::JSObject* jsDescriptionFromDecoder(JSC::JSGlobalObject*, IPC::Decoder&);
+
+    WeakPtr<JSIPC> m_jsIPC;
+    Type m_type;
+    JSContextRef m_context;
+    JSObjectRef m_callback;
+};
+
+class JSIPC : public RefCounted<JSIPC>, public CanMakeWeakPtr<JSIPC> {
+public:
     static Ref<JSIPC> create(WebPage& webPage, WebFrame& webFrame)
     {
         return adoptRef(*new JSIPC(webPage, webFrame));
@@ -78,6 +98,10 @@
     static const JSStaticFunction* staticFunctions();
     static const JSStaticValue* staticValues();
 
+    static void addMessageListener(JSMessageListener::Type, JSContextRef, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
+    static JSValueRef addIncomingMessageListener(JSContextRef, JSObjectRef, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
+    static JSValueRef addOutgoingMessageListener(JSContextRef, JSObjectRef, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
+
     static JSValueRef sendMessage(JSContextRef, JSObjectRef, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
     static JSValueRef sendSyncMessage(JSContextRef, JSObjectRef, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
 
@@ -92,6 +116,7 @@
 
     WeakPtr<WebPage> m_webPage;
     WeakPtr<WebFrame> m_webFrame;
+    Vector<UniqueRef<JSMessageListener>> m_messageListeners;
 };
 
 JSClassRef JSIPC::wrapperClass()
@@ -135,6 +160,8 @@
 const JSStaticFunction* JSIPC::staticFunctions()
 {
     static const JSStaticFunction functions[] = {
+        { "addIncomingMessageListener", addIncomingMessageListener, kJSPropertyAttributeDontDelete | kJSPropertyAttributeReadOnly },
+        { "addOutgoingMessageListener", addOutgoingMessageListener, kJSPropertyAttributeDontDelete | kJSPropertyAttributeReadOnly },
         { "sendMessage", sendMessage, kJSPropertyAttributeDontDelete | kJSPropertyAttributeReadOnly },
         { "sendSyncMessage", sendSyncMessage, kJSPropertyAttributeDontDelete | kJSPropertyAttributeReadOnly },
         { 0, 0, 0 }
@@ -169,6 +196,12 @@
     return WTF::nullopt;
 }
 
+static JSValueRef createTypeError(JSContextRef context, const String& message)
+{
+    JSC::JSLockHolder lock(toJS(context)->vm());
+    return toRef(JSC::createTypeError(toJS(context), message));
+}
+
 static RefPtr<IPC::Connection> processTargetFromArgument(JSC::JSGlobalObject* globalObject, JSValueRef valueRef, JSValueRef* exception)
 {
     auto scope = DECLARE_CATCH_SCOPE(globalObject->vm());
@@ -189,6 +222,53 @@
     return nullptr;
 }
 
+void JSIPC::addMessageListener(JSMessageListener::Type type, JSContextRef context, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+    auto* globalObject = toJS(context);
+    JSC::JSLockHolder lock(globalObject->vm());
+    auto jsIPC = makeRefPtr(toWrapped(context, thisObject));
+    if (!jsIPC) {
+        *exception = createTypeError(context, "Wrong type"_s);
+        return;
+    }
+
+    if (argumentCount < 1) {
+        *exception = createTypeError(context, "Must specify the target process as the first argument"_s);
+        return;
+    }
+
+    auto connection = processTargetFromArgument(globalObject, arguments[0], exception);
+    if (!connection)
+        return;
+
+    std::unique_ptr<JSMessageListener> listener;
+    if (argumentCount >= 2 && JSValueIsObject(context, arguments[1])) {
+        auto listenerObjectRef = JSValueToObject(context, arguments[1], exception);
+        if (JSObjectIsFunction(context, listenerObjectRef))
+            listener = makeUnique<JSMessageListener>(*jsIPC, type, context, listenerObjectRef);
+    }
+
+    if (!listener) {
+        *exception = createTypeError(context, "Must specify a callback function as the second argument"_s);
+        return;
+    }
+
+    connection->addMessageObserver(*listener);
+    jsIPC->m_messageListeners.append(makeUniqueRefFromNonNullUniquePtr(WTFMove(listener)));
+}
+
+JSValueRef JSIPC::addIncomingMessageListener(JSContextRef context, JSObjectRef, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+    addMessageListener(JSMessageListener::Type::Incoming, context, thisObject, argumentCount, arguments, exception);
+    return JSValueMakeUndefined(context);
+}
+
+JSValueRef JSIPC::addOutgoingMessageListener(JSContextRef context, JSObjectRef, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+    addMessageListener(JSMessageListener::Type::Outgoing, context, thisObject, argumentCount, arguments, exception);
+    return JSValueMakeUndefined(context);
+}
+
 static Optional<uint64_t> destinationIDFromArgument(JSC::JSGlobalObject* globalObject, JSValueRef valueRef, JSValueRef* exception)
 {
     auto jsValue = toJS(globalObject, valueRef);
@@ -235,12 +315,6 @@
     return true;
 }
 
-static JSValueRef createTypeError(JSContextRef context, const String& message)
-{
-    JSC::JSLockHolder lock(toJS(context)->vm());
-    return toRef(JSC::createTypeError(toJS(context), message));
-}
-
 template<typename PointType> bool encodePointType(IPC::Encoder& encoder, JSC::JSGlobalObject* globalObject, JSC::JSObject* jsObject, JSC::CatchScope& scope)
 {
     auto& vm = globalObject->vm();
@@ -804,6 +878,104 @@
     return toRef(vm, messagesObject);
 }
 
+JSMessageListener::JSMessageListener(JSIPC& jsIPC, Type type, JSContextRef context, JSObjectRef callback)
+    : m_jsIPC(makeWeakPtr(jsIPC))
+    , m_type(type)
+    , m_context(context)
+    , m_callback(callback)
+{
+    auto* globalObject = toJS(context);
+    auto& vm = globalObject->vm();
+    JSC::JSLockHolder lock(vm);
+
+    auto catchScope = DECLARE_CATCH_SCOPE(vm);
+
+    // We can't retain the global context here as that would cause a leak
+    // since this object is supposed to live as long as the global object is alive.
+    JSC::PrivateName uniquePrivateName;
+    globalObject->putDirect(vm, uniquePrivateName, toJS(globalObject, callback));
+}
+
+void JSMessageListener::didReceiveMessage(const IPC::Decoder& decoder)
+{
+    if (m_type != Type::Incoming)
+        return;
+
+    RELEASE_ASSERT(m_jsIPC);
+    auto protectOwnerOfThis = makeRef(*m_jsIPC);
+    auto* globalObject = toJS(m_context);
+    JSC::JSLockHolder lock(globalObject->vm());
+
+    auto mutableDecoder = IPC::Decoder::create(decoder.buffer(), decoder.length(), nullptr, { });
+    auto* description = jsDescriptionFromDecoder(globalObject, *mutableDecoder);
+
+    JSValueRef arguments[] = { description ? toRef(globalObject, description) : JSValueMakeUndefined(m_context) };
+    JSObjectCallAsFunction(m_context, m_callback, m_callback, std::size(arguments), arguments, nullptr);
+}
+
+void JSMessageListener::willSendMessage(const IPC::Encoder& encoder, OptionSet<IPC::SendOption>)
+{
+    if (m_type != Type::Outgoing)
+        return;
+
+    RELEASE_ASSERT(m_jsIPC);
+    auto protectOwnerOfThis = makeRef(*m_jsIPC);
+    auto* globalObject = toJS(m_context);
+    JSC::JSLockHolder lock(globalObject->vm());
+
+    auto decoder = IPC::Decoder::create(encoder.buffer(), encoder.bufferSize(), nullptr, { });
+    auto* description = jsDescriptionFromDecoder(globalObject, *decoder);
+
+    JSValueRef arguments[] = { description ? toRef(globalObject, description) : JSValueMakeUndefined(m_context) };
+    JSObjectCallAsFunction(m_context, m_callback, m_callback, WTF_ARRAY_LENGTH(arguments), arguments, nullptr);
+}
+
+JSC::JSObject* JSMessageListener::jsDescriptionFromDecoder(JSC::JSGlobalObject* globalObject, IPC::Decoder& decoder)
+{
+    auto& vm = globalObject->vm();
+    auto scope = DECLARE_CATCH_SCOPE(vm);
+
+    auto* jsResult = constructEmptyObject(globalObject, globalObject->objectPrototype());
+    RETURN_IF_EXCEPTION(scope, nullptr);
+
+    jsResult->putDirect(vm, JSC::Identifier::fromString(vm, "name"), JSC::JSValue(static_cast<unsigned>(decoder.messageName())));
+    RETURN_IF_EXCEPTION(scope, nullptr);
+
+    jsResult->putDirect(vm, JSC::Identifier::fromString(vm, "description"), JSC::jsString(vm, IPC::description(decoder.messageName())));
+    RETURN_IF_EXCEPTION(scope, nullptr);
+
+    jsResult->putDirect(vm, JSC::Identifier::fromString(vm, "destinationID"), JSC::JSValue(decoder.destinationID()));
+    RETURN_IF_EXCEPTION(scope, nullptr);
+
+    if (decoder.isSyncMessage()) {
+        if (uint64_t syncRequestID = 0; decoder.decode(syncRequestID)) {
+            jsResult->putDirect(vm, JSC::Identifier::fromString(vm, "syncRequestID"), JSC::JSValue(syncRequestID));
+            RETURN_IF_EXCEPTION(scope, nullptr);
+        }
+    } else if (messageReplyArgumentDescriptions(decoder.messageName())) {
+        if (uint64_t listenerID = 0; decoder.decode(listenerID)) {
+            jsResult->putDirect(vm, JSC::Identifier::fromString(vm, "listenerID"), JSC::JSValue(listenerID));
+            RETURN_IF_EXCEPTION(scope, nullptr);
+        }
+    }
+
+    auto arrayBuffer = JSC::ArrayBuffer::create(decoder.buffer(), decoder.length());
+    if (auto* structure = globalObject->arrayBufferStructure(arrayBuffer->sharingMode())) {
+        if (auto* jsArrayBuffer = JSC::JSArrayBuffer::create(vm, structure, WTFMove(arrayBuffer))) {
+            jsResult->putDirect(vm, JSC::Identifier::fromString(vm, "buffer"), jsArrayBuffer);
+            RETURN_IF_EXCEPTION(scope, nullptr);
+        }
+    }
+
+    auto jsReplyArguments = jsValueForArguments(globalObject, decoder.messageName(), decoder);
+    if (jsReplyArguments) {
+        jsResult->putDirect(vm, vm.propertyNames->arguments, jsReplyArguments->isEmpty() ? JSC::jsNull() : *jsReplyArguments);
+        RETURN_IF_EXCEPTION(scope, nullptr);
+    }
+
+    return jsResult;
+}
+
 void inject(WebPage& webPage, WebFrame& webFrame, WebCore::DOMWrapperWorld& world)
 {
     auto* globalObject = webFrame.coreFrame()->script().globalObject(world);

Modified: trunk/Tools/ChangeLog (268847 => 268848)


--- trunk/Tools/ChangeLog	2020-10-22 00:40:55 UTC (rev 268847)
+++ trunk/Tools/ChangeLog	2020-10-22 00:41:50 UTC (rev 268848)
@@ -1,3 +1,17 @@
+2020-10-21  Ryosuke Niwa  <[email protected]>
+
+        IPC testing API should have the capability to observe messages being sent and received
+        https://bugs.webkit.org/show_bug.cgi?id=217870
+
+        Reviewed by Darin Adler.
+
+        Added tests to intercept IPC messages sent and received by WebContent process.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm:
+        (IPCTestingAPI.CanInterceptAlert):
+        (IPCTestingAPI.CanInterceptHasStorageAccess):
+        (IPCTestingAPI.CanInterceptFindString):
+
 2020-10-21  Jonathan Bedard  <[email protected]>
 
         [webkitpy] Use webkitcorepy's autoinstaller for beautifulsoup

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm (268847 => 268848)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm	2020-10-22 00:40:55 UTC (rev 268847)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm	2020-10-22 00:41:50 UTC (rev 268848)
@@ -208,4 +208,89 @@
     EXPECT_STREQ([[webView stringByEvaluatingJavaScript:@"args[2].type"] UTF8String], "String");
 }
 
+TEST(IPCTestingAPI, CanInterceptAlert)
+{
+    auto webView = createWebViewWithIPCTestingAPI();
+
+    auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]);
+    [webView setUIDelegate:delegate.get()];
+
+    done = false;
+    [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>messages = []; IPC.addOutgoingMessageListener('UI', (message) => messages.push(message)); alert('ok');</script>"];
+    TestWebKitAPI::Util::run(&done);
+
+    EXPECT_STREQ([alertMessage UTF8String], "ok");
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"messages = messages.filter((message) => message.name == IPC.messages.WebPageProxy_RunJavaScriptAlert.name); messages.length"].UTF8String, "1");
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"messages[0].description"].UTF8String, "WebPageProxy_RunJavaScriptAlert");
+    EXPECT_EQ([webView stringByEvaluatingJavaScript:@"messages[0].arguments.length"].intValue, 3);
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"typeof(messages[0].syncRequestID)"].UTF8String, "number");
+    EXPECT_EQ([webView stringByEvaluatingJavaScript:@"messages[0].destinationID"].intValue,
+        [webView stringByEvaluatingJavaScript:@"IPC.webPageProxyID.toString()"].intValue);
+}
+
+#if ENABLE(RESOURCE_LOAD_STATISTICS)
+TEST(IPCTestingAPI, CanInterceptHasStorageAccess)
+{
+    auto webView = createWebViewWithIPCTestingAPI();
+
+    auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]);
+    [webView setUIDelegate:delegate.get()];
+
+    done = false;
+    promptResult = @"foo";
+    [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>let targetMessage = {}; const messageName = IPC.messages.NetworkConnectionToWebProcess_HasStorageAccess.name;"
+        "IPC.addOutgoingMessageListener('Networking', (currentMessage) => { if (currentMessage.name == messageName) targetMessage = currentMessage; });"
+        "IPC.sendMessage('Networking', 0, messageName, [{type: 'RegistrableDomain', value: 'https://ipctestingapi.com'}, {type: 'RegistrableDomain', value: 'https://webkit.org'},"
+        "{type: 'uint64_t', value: IPC.frameID}, {type: 'uint64_t', value: IPC.pageID}]).then((result) => alert(JSON.stringify(result.arguments)));</script>"];
+    TestWebKitAPI::Util::run(&done);
+
+    EXPECT_STREQ([alertMessage UTF8String], "[{\"type\":\"bool\",\"value\":false}]");
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"targetMessage.description"].UTF8String, "NetworkConnectionToWebProcess_HasStorageAccess");
+    EXPECT_EQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments.length"].intValue, 4);
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments[0].type"].UTF8String, "RegistrableDomain");
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments[0].value"].UTF8String, "ipctestingapi.com");
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments[1].type"].UTF8String, "RegistrableDomain");
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments[1].value"].UTF8String, "webkit.org");
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments[2].type"].UTF8String, "uint64_t");
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments[2].value"].UTF8String, [webView stringByEvaluatingJavaScript:@"IPC.frameID.toString()"].UTF8String);
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments[3].type"].UTF8String, "uint64_t");
+    EXPECT_EQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments[3].value"].intValue, [webView stringByEvaluatingJavaScript:@"IPC.pageID.toString()"].intValue);
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"typeof(targetMessage.syncRequestID)"].UTF8String, "undefined");
+    EXPECT_EQ([webView stringByEvaluatingJavaScript:@"targetMessage.destinationID"].intValue, 0);
+}
 #endif
+
+TEST(IPCTestingAPI, CanInterceptFindString)
+{
+    auto webView = createWebViewWithIPCTestingAPI();
+
+    auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]);
+    [webView setUIDelegate:delegate.get()];
+
+    [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><body><p>hello</p><script>messages = []; IPC.addIncomingMessageListener('UI', (message) => messages.push(message));</script>"];
+
+    done = false;
+    auto findConfiguration = adoptNS([[WKFindConfiguration alloc] init]);
+    [webView findString:@"hello" withConfiguration:findConfiguration.get() completionHandler:^(WKFindResult *result) {
+        EXPECT_TRUE(result.matchFound);
+        EXPECT_TRUE([webView selectionRangeHasStartOffset:0 endOffset:5]);
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"messages = messages.filter((message) => message.name == IPC.messages.WebPage_FindString.name); messages.length"].UTF8String, "1");
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"messages[0].description"].UTF8String, "WebPage_FindString");
+    EXPECT_EQ([webView stringByEvaluatingJavaScript:@"args = messages[0].arguments; args.length"].intValue, 3);
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"args[0].type"].UTF8String, "String");
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"args[0].value"].UTF8String, "hello");
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"args[1].type"].UTF8String, "uint16_t");
+    EXPECT_EQ([webView stringByEvaluatingJavaScript:@"args[1].value"].intValue, 0x11);
+    EXPECT_EQ([webView stringByEvaluatingJavaScript:@"args[1].isOptionSet"].boolValue, YES);
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"args[2].type"].UTF8String, "uint32_t");
+    EXPECT_EQ([webView stringByEvaluatingJavaScript:@"args[2].value"].intValue, 1);
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"typeof(messages[0].syncRequestID)"].UTF8String, "undefined");
+    EXPECT_EQ([webView stringByEvaluatingJavaScript:@"messages[0].destinationID"].intValue,
+        [webView stringByEvaluatingJavaScript:@"IPC.webPageProxyID.toString()"].intValue);
+}
+
+#endif
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to