Make it possible to send an arbitrary IPC message from JavaScript
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 9 Oct 2020 00:48:35 +0000 (00:48 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 9 Oct 2020 00:48:35 +0000 (00:48 +0000)
https://bugs.webkit.org/show_bug.cgi?id=217423
<rdar://problem/69969351>

Reviewed by Geoffrey Garen.

Source/JavaScriptCore:

Added a helper function to get uint64_t out of BigInt.

* runtime/JSBigInt.cpp:
(JSC::JSBigInt::toUint64Heap): Added.
* runtime/JSBigInt.h:
(JSC::JSBigInt::toUint64): Added.

Source/WebKit:

This patch introduces the JavaScript API (window.IPC) to send IPC out of WebContent process.
The feature is compiled in under ASAN and Debug builds and can be enabled at runtime.

window.IPC has two methods: sendMessage and sendSyncMessage which sends an async and sync IPC respectively.
It takes the destination process name (UI, GPU, or Networking), the destination ID (e.g. WebPageProxy ID),
message ID, timeout for sendSyncMessage, and optionally IPC message arguments. The message arguments can be
passed in as a TypedArray or ArrayBuffer, or a JavaScript array that recursively describes encoded objects.

Each object can be either a TypedArray or ArrayBuffer, which will be treated as encoded message, an array
which will be encoded as a Vector with each item within the array encoded recursively, or a dictionary which
describes a specific type.

When a specific type is described via a dictionary, "value" is encoed based on "type" as follows:
 - When "type" is "String", "value" is encoded as a WTF::String, treating null or undefined as a null string.
 - When "type" is "bool", "int8_t", "int16_t", "int32_t", "int64_t", "uint8_t", "uint16_t", "uint32_t",
   or "uint64_t", "value" (which can be BigInt or a number) is encoded as the respective C++ type.
 - When "type" is "RGBA", "value" is used as PackedColor::RGBA to construct WebCore::Color to be encoded.
 - When "type" is "IntRect" or "FloatRect", "x", "y", "width", and "height" are treated as respective values
   of IntRect or FloatRect C++ objects, and the constructed *Rect is encoded.
 - When "type" is "FrameInfoData", the context object's WebFrame's FrameInfoData is encoded.

The list of IPC messages are exposed on window.IPC.messages, and VisitedLinkStore ID, WebPageProxy ID,
and frame identifiers are also exposed as static variables on window.IPC.

* Sources.txt:
* WebKit.xcodeproj/project.pbxproj:
* WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp:
(WebKit::WebFrameLoaderClient::dispatchDidClearWindowObjectInWorld): Inject the API if enabled.
* WebProcess/WebPage/IPCTestingAPI.cpp: Added.
(WebKit::IPCTestingAPI::JSIPC::create): Added.
(WebKit::IPCTestingAPI::JSIPC::webFrame): Added.
(WebKit::IPCTestingAPI::JSIPC::JSIPC): Added.
(WebKit::IPCTestingAPI::JSIPC::wrapperClass): Added.
(WebKit::IPCTestingAPI::JSIPC::unwrap): Added.
(WebKit::IPCTestingAPI::JSIPC::toWrapped): Added.
(WebKit::IPCTestingAPI::JSIPC::initialize): Added.
(WebKit::IPCTestingAPI::JSIPC::finalize): Added.
(WebKit::IPCTestingAPI::JSIPC::staticFunctions): Added.
(WebKit::IPCTestingAPI::JSIPC::staticValues): Added.
(WebKit::IPCTestingAPI::convertToUint64): Added.
(WebKit::IPCTestingAPI::processTargetFromArgument): Added.
(WebKit::IPCTestingAPI::destinationIDFromArgument): Added.
(WebKit::IPCTestingAPI::messageIDFromArgument): Added.
(WebKit::IPCTestingAPI::encodeTypedArray): Added.
(WebKit::IPCTestingAPI::createTypeError): Added.
(WebKit::IPCTestingAPI::encodeRectType): Added.
(WebKit::IPCTestingAPI::encodeIntegralType): Added.
(WebKit::IPCTestingAPI::VectorEncodeHelper::encode const): Added.
(WebKit::IPCTestingAPI::encodeArgument): Added.
(WebKit::IPCTestingAPI::JSIPC::sendMessage): Added.
(WebKit::IPCTestingAPI::JSIPC::sendSyncMessage): Added.
(WebKit::IPCTestingAPI::JSIPC::visitedLinkStoreID): Added.
(WebKit::IPCTestingAPI::JSIPC::webPageProxyID): Added.
(WebKit::IPCTestingAPI::JSIPC::frameIdentifier): Added.
(WebKit::IPCTestingAPI::JSIPC::retrieveID): Added.
(WebKit::IPCTestingAPI::JSIPC::messages): Added.
(WebKit::IPCTestingAPI::inject):
* WebProcess/WebPage/IPCTestingAPI.h: Added.
* WebProcess/WebPage/WebFrame.h:
* WebProcess/WebPage/WebPage.cpp:
(WebKit::m_limitsNavigationsToAppBoundDomains):
(WebKit::WebPage::updatePreferences):
* WebProcess/WebPage/WebPage.h:
(WebKit::WebPage::ipcTestingAPIEnabled const):
(WebKit::WebPage::webPageProxyID const):
(WebKit::WebPage::visitedLinkTableID const):

Source/WTF:

Added a compile time flag (ENABLE_IPC_TESTING_API) and a runtime flag (IPCTestingAPIEnabled)
for the JavaScript API to test IPC.

* Scripts/GeneratePreferences.rb:
(Preference::nameLower): Keep IPC uppercase.
* Scripts/Preferences/WebPreferencesInternal.yaml: Added IPCTestingAPIEnabled.
* wtf/PlatformEnable.h: Added ENABLE_IPC_TESTING_API.

Tools:

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm: Added.
(-[IPCTestingAPIDelegate webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:]):
(TEST):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@268239 268f45cc-cd09-0410-ab3c-d52691b4dbfc

19 files changed:
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/JSBigInt.cpp
Source/JavaScriptCore/runtime/JSBigInt.h
Source/WTF/ChangeLog
Source/WTF/Scripts/GeneratePreferences.rb
Source/WTF/Scripts/Preferences/WebPreferencesInternal.yaml
Source/WTF/wtf/PlatformEnable.h
Source/WebKit/ChangeLog
Source/WebKit/Sources.txt
Source/WebKit/WebKit.xcodeproj/project.pbxproj
Source/WebKit/WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp
Source/WebKit/WebProcess/WebPage/IPCTestingAPI.cpp [new file with mode: 0644]
Source/WebKit/WebProcess/WebPage/IPCTestingAPI.h [new file with mode: 0644]
Source/WebKit/WebProcess/WebPage/WebFrame.h
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm [new file with mode: 0644]

index 96fadf81aa480e8567b3d771dcac16bab6850fc0..af92a4bb2085fb602dc3a76b912189831cf3bfbd 100644 (file)
@@ -1,3 +1,18 @@
+2020-10-08  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Make it possible to send an arbitrary IPC message from JavaScript
+        https://bugs.webkit.org/show_bug.cgi?id=217423
+        <rdar://problem/69969351>
+
+        Reviewed by Geoffrey Garen.
+
+        Added a helper function to get uint64_t out of BigInt.
+
+        * runtime/JSBigInt.cpp:
+        (JSC::JSBigInt::toUint64Heap): Added.
+        * runtime/JSBigInt.h:
+        (JSC::JSBigInt::toUint64): Added.
+
 2020-10-07  Yusuke Suzuki  <ysuzuki@apple.com>
 
         [JSC] Restrict more ptr-tagging and avoid using OperationPtrTag for JIT code
index dba822b3ea6a9be2891e1a80979acd0d236e8593..1f3fa4eff471eae4921d58dd3bd2534908794e63 100644 (file)
@@ -3049,6 +3049,29 @@ JSValue JSBigInt::asUintN(JSGlobalObject* globalObject, uint64_t n, int32_t bigI
 }
 #endif
 
+Optional<uint64_t> JSBigInt::toUint64Heap(JSBigInt* bigInt)
+{
+    auto length = bigInt->length();
+    if (!length)
+        return 0;
+    if (bigInt->sign())
+        return WTF::nullopt;
+
+    static_assert(sizeof(uint64_t) == sizeof(Digit) || sizeof(uint64_t) == sizeof(Digit) * 2, "Digit must be either 32-bit or 64-bit");
+    if (sizeof(uint64_t) == sizeof(Digit)) {
+        if (length > 1)
+            return WTF::nullopt;
+        return bigInt->digit(0);
+    }
+
+    if (length > 2)
+        return WTF::nullopt;
+    uint64_t result = bigInt->digit(0);
+    if (length == 1)
+        result += static_cast<uint64_t>(bigInt->digit(0)) << 32;
+    return result;
+}
+
 static ALWAYS_INLINE unsigned computeHash(JSBigInt::Digit* digits, unsigned length, bool sign)
 {
     Hasher hasher;
index 144dc4b7c05a187a26ee124927419f096c1df2fd..5ec4e0e462c26ba08437e8b3f0da2c8228ef7ab6 100644 (file)
@@ -73,7 +73,7 @@ public:
     static JSBigInt* tryCreateFrom(VM&, int32_t value);
     static JSBigInt* createFrom(JSGlobalObject*, uint32_t value);
     static JSBigInt* createFrom(JSGlobalObject*, int64_t value);
-    static JSBigInt* createFrom(JSGlobalObject*, uint64_t value);
+    JS_EXPORT_PRIVATE static JSBigInt* createFrom(JSGlobalObject*, uint64_t value);
     static JSBigInt* createFrom(JSGlobalObject*, bool value);
     static JSBigInt* createFrom(JSGlobalObject*, double value);
 
@@ -422,6 +422,20 @@ public:
     static JSValue asUintN(JSGlobalObject*, uint64_t numberOfBits, int32_t bigIntAsInt32);
 #endif
 
+    static Optional<uint64_t> toUint64(JSValue bigInt)
+    {
+        ASSERT(bigInt.isBigInt());
+#if USE(BIGINT32)
+        if (bigInt.isBigInt32()) {
+            auto value = bigInt.bigInt32AsInt32();
+            if (value < 0)
+                return WTF::nullopt;
+            return value;
+        }
+#endif
+        return toUint64Heap(jsCast<JSBigInt*>(bigInt));
+    }
+
     Digit digit(unsigned);
     void setDigit(unsigned, Digit); // Use only when initializing.
     JS_EXPORT_PRIVATE JSBigInt* rightTrim(JSGlobalObject*);
@@ -576,6 +590,8 @@ private:
     template <typename BigIntImpl>
     static ImplResult truncateAndSubFromPowerOfTwo(JSGlobalObject*, int32_t, BigIntImpl, bool resultSign);
 
+    JS_EXPORT_PRIVATE static Optional<uint64_t> toUint64Heap(JSBigInt*);
+
     inline static size_t offsetOfData()
     {
         return OBJECT_OFFSETOF(JSBigInt, m_data);
index 25d13234ed30a8dbc6adf99ce1524d3851fc3b81..39e999bd34d58cb0e7cf8244ec6f814f11cc089f 100644 (file)
@@ -1,3 +1,19 @@
+2020-10-08  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Make it possible to send an arbitrary IPC message from JavaScript
+        https://bugs.webkit.org/show_bug.cgi?id=217423
+        <rdar://problem/69969351>
+
+        Reviewed by Geoffrey Garen.
+
+        Added a compile time flag (ENABLE_IPC_TESTING_API) and a runtime flag (IPCTestingAPIEnabled)
+        for the JavaScript API to test IPC.
+
+        * Scripts/GeneratePreferences.rb:
+        (Preference::nameLower): Keep IPC uppercase.
+        * Scripts/Preferences/WebPreferencesInternal.yaml: Added IPCTestingAPIEnabled.
+        * wtf/PlatformEnable.h: Added ENABLE_IPC_TESTING_API.
+
 2020-10-08  Alex Christensen  <achristensen@webkit.org>
 
         REGRESSION (r267763): [ iOS wk2 ] http/tests/in-app-browser-privacy/non-app-bound-domain-does-not-get-app-bound-session.html is a constant failure
index 1c86b9230f4b6b4f78cbb441670e6d999607ee3e..8fa2e1d0996fdb9e9b79d64da1a86f2cdf9bdf7e 100644 (file)
@@ -120,7 +120,7 @@ class Preference
       @getter
     elsif @name.start_with?("VP")
       @name[0..1].downcase + @name[2..@name.length]
-    elsif @name.start_with?("CSS", "XSS", "FTP", "DOM", "DNS", "PDF", "ICE")
+    elsif @name.start_with?("CSS", "DOM", "DNS", "FTP", "ICE", "IPC", "PDF", "XSS")
       @name[0..2].downcase + @name[3..@name.length]
     elsif @name.start_with?("HTTP")
       @name[0..3].downcase + @name[4..@name.length]
index 8ec2a6008b35a90e9ed714cfc1909bbec6766005..2943afed213392d072b86d567d63855a2faa3a6d 100644 (file)
@@ -279,6 +279,17 @@ ICECandidateFilteringEnabled:
     WebKit:
       default: true
 
+IPCTestingAPIEnabled:
+  type: bool
+  humanReadableName: "IPC Testing API"
+  humanReadableDescription: "Enable IPC Testing API for JavaScript"
+  webcoreBinding: none
+  condition: ENABLE(IPC_TESTING_API)
+  exposed: [ WebKit ]
+  defaultValue:
+    WebKit:
+      default: false
+
 InputTypeColorEnabled:
   type: bool
   humanReadableName: "Color Inputs"
index 4a225bd7b7604248e33a6275c9c9dc884606cad0..f447638b418b88dd3f37bf46274ce3cb03784251 100644 (file)
 #define ENABLE_INPUT_TYPE_WEEK 0
 #endif
 
+#if !defined(ENABLE_IPC_TESTING_API)
+/* Enable IPC testing on all ASAN builds and debug builds. */
+#if ASAN_ENABLED || !defined(NDEBUG)
+#define ENABLE_IPC_TESTING_API 1
+#endif
+#endif
+
 #if ENABLE(INPUT_TYPE_DATE) || ENABLE(INPUT_TYPE_DATETIMELOCAL) || ENABLE(INPUT_TYPE_MONTH) || ENABLE(INPUT_TYPE_TIME) || ENABLE(INPUT_TYPE_WEEK)
 #if !defined(ENABLE_DATE_AND_TIME_INPUT_TYPES)
 #define ENABLE_DATE_AND_TIME_INPUT_TYPES 1
index 256a1d4264fe15976fdf34e7f6fcf21710539bea..edf9934bfcd335ee791d9974f587b9d2a70e2bd0 100644 (file)
@@ -1,3 +1,78 @@
+2020-10-08  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Make it possible to send an arbitrary IPC message from JavaScript
+        https://bugs.webkit.org/show_bug.cgi?id=217423
+        <rdar://problem/69969351>
+
+        Reviewed by Geoffrey Garen.
+
+        This patch introduces the JavaScript API (window.IPC) to send IPC out of WebContent process.
+        The feature is compiled in under ASAN and Debug builds and can be enabled at runtime.
+
+        window.IPC has two methods: sendMessage and sendSyncMessage which sends an async and sync IPC respectively.
+        It takes the destination process name (UI, GPU, or Networking), the destination ID (e.g. WebPageProxy ID),
+        message ID, timeout for sendSyncMessage, and optionally IPC message arguments. The message arguments can be
+        passed in as a TypedArray or ArrayBuffer, or a JavaScript array that recursively describes encoded objects.
+
+        Each object can be either a TypedArray or ArrayBuffer, which will be treated as encoded message, an array
+        which will be encoded as a Vector with each item within the array encoded recursively, or a dictionary which
+        describes a specific type.
+
+        When a specific type is described via a dictionary, "value" is encoed based on "type" as follows:
+         - When "type" is "String", "value" is encoded as a WTF::String, treating null or undefined as a null string.
+         - When "type" is "bool", "int8_t", "int16_t", "int32_t", "int64_t", "uint8_t", "uint16_t", "uint32_t",
+           or "uint64_t", "value" (which can be BigInt or a number) is encoded as the respective C++ type.
+         - When "type" is "RGBA", "value" is used as PackedColor::RGBA to construct WebCore::Color to be encoded.
+         - When "type" is "IntRect" or "FloatRect", "x", "y", "width", and "height" are treated as respective values
+           of IntRect or FloatRect C++ objects, and the constructed *Rect is encoded. 
+         - When "type" is "FrameInfoData", the context object's WebFrame's FrameInfoData is encoded.
+
+        The list of IPC messages are exposed on window.IPC.messages, and VisitedLinkStore ID, WebPageProxy ID,
+        and frame identifiers are also exposed as static variables on window.IPC.
+
+        * Sources.txt:
+        * WebKit.xcodeproj/project.pbxproj:
+        * WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp:
+        (WebKit::WebFrameLoaderClient::dispatchDidClearWindowObjectInWorld): Inject the API if enabled.
+        * WebProcess/WebPage/IPCTestingAPI.cpp: Added.
+        (WebKit::IPCTestingAPI::JSIPC::create): Added.
+        (WebKit::IPCTestingAPI::JSIPC::webFrame): Added.
+        (WebKit::IPCTestingAPI::JSIPC::JSIPC): Added.
+        (WebKit::IPCTestingAPI::JSIPC::wrapperClass): Added.
+        (WebKit::IPCTestingAPI::JSIPC::unwrap): Added.
+        (WebKit::IPCTestingAPI::JSIPC::toWrapped): Added.
+        (WebKit::IPCTestingAPI::JSIPC::initialize): Added.
+        (WebKit::IPCTestingAPI::JSIPC::finalize): Added.
+        (WebKit::IPCTestingAPI::JSIPC::staticFunctions): Added.
+        (WebKit::IPCTestingAPI::JSIPC::staticValues): Added.
+        (WebKit::IPCTestingAPI::convertToUint64): Added.
+        (WebKit::IPCTestingAPI::processTargetFromArgument): Added.
+        (WebKit::IPCTestingAPI::destinationIDFromArgument): Added.
+        (WebKit::IPCTestingAPI::messageIDFromArgument): Added.
+        (WebKit::IPCTestingAPI::encodeTypedArray): Added.
+        (WebKit::IPCTestingAPI::createTypeError): Added.
+        (WebKit::IPCTestingAPI::encodeRectType): Added.
+        (WebKit::IPCTestingAPI::encodeIntegralType): Added.
+        (WebKit::IPCTestingAPI::VectorEncodeHelper::encode const): Added.
+        (WebKit::IPCTestingAPI::encodeArgument): Added.
+        (WebKit::IPCTestingAPI::JSIPC::sendMessage): Added.
+        (WebKit::IPCTestingAPI::JSIPC::sendSyncMessage): Added.
+        (WebKit::IPCTestingAPI::JSIPC::visitedLinkStoreID): Added.
+        (WebKit::IPCTestingAPI::JSIPC::webPageProxyID): Added.
+        (WebKit::IPCTestingAPI::JSIPC::frameIdentifier): Added.
+        (WebKit::IPCTestingAPI::JSIPC::retrieveID): Added.
+        (WebKit::IPCTestingAPI::JSIPC::messages): Added.
+        (WebKit::IPCTestingAPI::inject):
+        * WebProcess/WebPage/IPCTestingAPI.h: Added.
+        * WebProcess/WebPage/WebFrame.h:
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::m_limitsNavigationsToAppBoundDomains):
+        (WebKit::WebPage::updatePreferences):
+        * WebProcess/WebPage/WebPage.h:
+        (WebKit::WebPage::ipcTestingAPIEnabled const):
+        (WebKit::WebPage::webPageProxyID const):
+        (WebKit::WebPage::visitedLinkTableID const):
+
 2020-10-08  Peng Liu  <peng.liu6@apple.com>
 
         [Media in GPU Process] Cannot activate or deactivate an audio session
index bc2c4943aebe5ccaf8b947ed71ae030f4f32d983..5f930708c504e9a619b9058eb924fd766cf9da84 100644 (file)
@@ -652,6 +652,7 @@ WebProcess/WebCoreSupport/WebSpeechSynthesisClient.cpp
 WebProcess/WebPage/DrawingArea.cpp
 WebProcess/WebPage/EventDispatcher.cpp
 WebProcess/WebPage/FindController.cpp
+WebProcess/WebPage/IPCTestingAPI.cpp
 WebProcess/WebPage/PageBanner.cpp
 WebProcess/WebPage/VisitedLinkTableController.cpp
 WebProcess/WebPage/WebBackForwardListProxy.cpp
index 65ff86db3d23f0caa28b2e6c735ecf439fb1d069..c1a998eb4dae2ad41aaf62a5f3de53aa67a80793 100644 (file)
                9B02E0CD235EB967004044B2 /* _WKTextManipulationToken.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _WKTextManipulationToken.h; sourceTree = "<group>"; };
                9B02E0CE235EBB14004044B2 /* _WKTextManipulationToken.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKTextManipulationToken.mm; sourceTree = "<group>"; };
                9B02E0D0235EBCCA004044B2 /* _WKTextManipulationItem.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKTextManipulationItem.mm; sourceTree = "<group>"; };
+               9B033E71252580F300501071 /* IPCTestingAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IPCTestingAPI.h; sourceTree = "<group>"; };
+               9B033E72252580F300501071 /* IPCTestingAPI.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IPCTestingAPI.cpp; sourceTree = "<group>"; };
                9B1229CB23FF25F2008CA751 /* RemoteAudioDestinationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RemoteAudioDestinationManager.h; sourceTree = "<group>"; };
                9B1229CC23FF25F2008CA751 /* RemoteAudioDestinationManager.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = RemoteAudioDestinationManager.cpp; sourceTree = "<group>"; };
                9B1229CF23FF2814008CA751 /* RemoteAudioDestinationManager.messages.in */ = {isa = PBXFileReference; lastKnownFileType = text; path = RemoteAudioDestinationManager.messages.in; sourceTree = "<group>"; };
                                1AA575FD1496B6F300A4EE06 /* EventDispatcher.messages.in */,
                                1A90C1F31264FD71003E44D4 /* FindController.cpp */,
                                1A90C1F21264FD71003E44D4 /* FindController.h */,
+                               9B033E72252580F300501071 /* IPCTestingAPI.cpp */,
+                               9B033E71252580F300501071 /* IPCTestingAPI.h */,
                                7C387433172F5615001BD88A /* PageBanner.cpp */,
                                7CF47FF917275C57008ACB91 /* PageBanner.h */,
                                2D819B99186275B3001F03D1 /* ViewGestureGeometryCollector.cpp */,
index 1b9a3a9acd53066475a37d00e92058e8d8cb28b4..66afdfcd900de1deb5c736cc58492df1a60cfe63 100644 (file)
@@ -32,6 +32,7 @@
 #include "FindController.h"
 #include "FormDataReference.h"
 #include "FrameInfoData.h"
+#include "IPCTestingAPI.h"
 #include "InjectedBundle.h"
 #include "InjectedBundleDOMWindowExtension.h"
 #include "InjectedBundleNavigationAction.h"
@@ -1743,6 +1744,11 @@ void WebFrameLoaderClient::dispatchDidClearWindowObjectInWorld(DOMWrapperWorld&
     if (!webPage)
         return;
 
+#if ENABLE(IPC_TESTING_API)
+    if (world.isNormal() && webPage->ipcTestingAPIEnabled())
+        IPCTestingAPI::inject(*webPage, m_frame.get(), world);
+#endif
+
     webPage->injectedBundleLoaderClient().didClearWindowObjectForFrame(*webPage, m_frame, world);
 
     WebAutomationSessionProxy* automationSessionProxy = WebProcess::singleton().automationSessionProxy();
diff --git a/Source/WebKit/WebProcess/WebPage/IPCTestingAPI.cpp b/Source/WebKit/WebProcess/WebPage/IPCTestingAPI.cpp
new file mode 100644 (file)
index 0000000..96448d1
--- /dev/null
@@ -0,0 +1,604 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "IPCTestingAPI.h"
+
+#if ENABLE(IPC_TESTING_API)
+
+#include "Encoder.h"
+#include "FrameInfoData.h"
+#include "GPUProcessConnection.h"
+#include "MessageNames.h"
+#include "NetworkProcessConnection.h"
+#include "WebCoreArgumentCoders.h"
+#include "WebFrame.h"
+#include "WebPage.h"
+#include "WebProcess.h"
+#include <JavaScriptCore/APICast.h>
+#include <JavaScriptCore/BuiltinNames.h>
+#include <JavaScriptCore/JSBigInt.h>
+#include <JavaScriptCore/JSClassRef.h>
+#include <JavaScriptCore/JSLock.h>
+#include <JavaScriptCore/JSObject.h>
+#include <JavaScriptCore/JSValueRef.h>
+#include <JavaScriptCore/JavaScript.h>
+#include <JavaScriptCore/OpaqueJSString.h>
+#include <WebCore/DOMWrapperWorld.h>
+#include <WebCore/Frame.h>
+#include <WebCore/ScriptController.h>
+
+namespace WebKit {
+
+namespace IPCTestingAPI {
+
+class JSIPC : public RefCounted<JSIPC> {
+public:
+    static Ref<JSIPC> create(WebPage& webPage, WebFrame& webFrame)
+    {
+        return adoptRef(*new JSIPC(webPage, webFrame));
+    }
+
+    static JSClassRef wrapperClass();
+
+    WebFrame* webFrame() { return m_webFrame.get(); }
+
+private:
+    JSIPC(WebPage& webPage, WebFrame& webFrame)
+        : m_webPage(makeWeakPtr(webPage))
+        , m_webFrame(makeWeakPtr(webFrame))
+    { }
+
+    static JSIPC* unwrap(JSObjectRef);
+    static JSIPC* toWrapped(JSContextRef, JSValueRef);
+    static void initialize(JSContextRef, JSObjectRef);
+    static void finalize(JSObjectRef);
+    static const JSStaticFunction* staticFunctions();
+    static const JSStaticValue* staticValues();
+
+    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);
+
+    static JSValueRef visitedLinkStoreID(JSContextRef, JSObjectRef, JSStringRef, JSValueRef* exception);
+    static JSValueRef webPageProxyID(JSContextRef, JSObjectRef, JSStringRef, JSValueRef* exception);
+    static JSValueRef frameIdentifier(JSContextRef, JSObjectRef, JSStringRef, JSValueRef* exception);
+    static JSValueRef retrieveID(JSContextRef, JSObjectRef thisObject, JSValueRef* exception, const WTF::Function<uint64_t(JSIPC&)>&);
+
+    static JSValueRef messages(JSContextRef, JSObjectRef, JSStringRef, JSValueRef* exception);
+
+    WeakPtr<WebPage> m_webPage;
+    WeakPtr<WebFrame> m_webFrame;
+};
+
+JSClassRef JSIPC::wrapperClass()
+{
+    static JSClassRef jsClass;
+    if (!jsClass) {
+        JSClassDefinition definition = kJSClassDefinitionEmpty;
+        definition.className = "UIProcess";
+        definition.parentClass = 0;
+        definition.staticValues = staticValues();
+        definition.staticFunctions = staticFunctions();
+        definition.initialize = initialize;
+        definition.finalize = finalize;
+        jsClass = JSClassCreate(&definition);
+    }
+    return jsClass;
+}
+
+inline JSIPC* JSIPC::unwrap(JSObjectRef object)
+{
+    return static_cast<JSIPC*>(JSObjectGetPrivate(object));
+}
+
+JSIPC* JSIPC::toWrapped(JSContextRef context, JSValueRef value)
+{
+    if (!context || !value || !JSValueIsObjectOfClass(context, value, wrapperClass()))
+        return nullptr;
+    return unwrap(JSValueToObject(context, value, 0));
+}
+
+void JSIPC::initialize(JSContextRef, JSObjectRef object)
+{
+    unwrap(object)->ref();
+}
+
+void JSIPC::finalize(JSObjectRef object)
+{
+    unwrap(object)->deref();
+}
+
+const JSStaticFunction* JSIPC::staticFunctions()
+{
+    static const JSStaticFunction functions[] = {
+        { "sendMessage", sendMessage, kJSPropertyAttributeDontDelete | kJSPropertyAttributeReadOnly },
+        { "sendSyncMessage", sendSyncMessage, kJSPropertyAttributeDontDelete | kJSPropertyAttributeReadOnly },
+        { 0, 0, 0 }
+    };
+    return functions;
+}
+
+const JSStaticValue* JSIPC::staticValues()
+{
+    static const JSStaticValue values[] = {
+        { "visitedLinkStoreID", visitedLinkStoreID, 0, kJSPropertyAttributeDontDelete | kJSPropertyAttributeReadOnly },
+        { "webPageProxyID", webPageProxyID, 0, kJSPropertyAttributeDontDelete | kJSPropertyAttributeReadOnly },
+        { "frameIdentifier", frameIdentifier, 0, kJSPropertyAttributeDontDelete | kJSPropertyAttributeReadOnly },
+        { "messages", messages, 0, kJSPropertyAttributeDontDelete | kJSPropertyAttributeReadOnly },
+        { 0, 0, 0, 0 }
+    };
+    return values;
+}
+
+static Optional<uint64_t> convertToUint64(JSC::JSValue jsValue)
+{
+    if (jsValue.isNumber()) {
+        double value = jsValue.asNumber();
+        if (value < 0 || trunc(value) != value)
+            return WTF::nullopt;
+        return value;
+    }
+    if (jsValue.isBigInt())
+        return JSC::JSBigInt::toUint64(jsValue);
+    return WTF::nullopt;
+}
+
+static RefPtr<IPC::Connection> processTargetFromArgument(JSC::JSGlobalObject* globalObject, JSValueRef valueRef, JSValueRef* exception)
+{
+    auto scope = DECLARE_CATCH_SCOPE(globalObject->vm());
+    auto name = toJS(globalObject, valueRef).toWTFString(globalObject);
+    if (scope.exception())
+        return nullptr;
+
+    if (name == "UI")
+        return WebProcess::singleton().parentProcessConnection();
+#if ENABLE(GPU_PROCESS)
+    if (name == "GPU")
+        return &WebProcess::singleton().ensureGPUProcessConnection().connection();
+#endif
+    if (name == "Networking")
+        return &WebProcess::singleton().ensureNetworkProcessConnection().connection();
+
+    *exception = toRef(JSC::createTypeError(globalObject, "Target process must be UI, GPU, or Networking"_s));
+    return nullptr;
+}
+
+static Optional<uint64_t> destinationIDFromArgument(JSC::JSGlobalObject* globalObject, JSValueRef valueRef, JSValueRef* exception)
+{
+    auto jsValue = toJS(globalObject, valueRef);
+    auto result = convertToUint64(jsValue);
+    if (!result)
+        *exception = toRef(JSC::createTypeError(globalObject, "destinationID must be an integer"_s));
+    return result;
+}
+
+static Optional<uint64_t> messageIDFromArgument(JSC::JSGlobalObject* globalObject, JSValueRef valueRef, JSValueRef* exception)
+{
+    auto jsValue = toJS(globalObject, valueRef);
+    auto result = convertToUint64(jsValue);
+    if (!result)
+        *exception = toRef(JSC::createTypeError(globalObject, "messageID must be an integer"_s));
+    return result;
+}
+
+static bool encodeTypedArray(IPC::Encoder& encoder, JSContextRef context, JSValueRef valueRef, JSTypedArrayType type, JSValueRef* exception)
+{
+    ASSERT(type != kJSTypedArrayTypeNone);
+
+    auto objectRef = JSValueToObject(context, valueRef, exception);
+    if (!objectRef)
+        return false;
+
+    void* buffer;
+    if (type == kJSTypedArrayTypeArrayBuffer)
+        buffer = JSObjectGetArrayBufferBytesPtr(context, objectRef, exception);
+    else
+        buffer = JSObjectGetTypedArrayBytesPtr(context, objectRef, exception);
+    if (!buffer)
+        return false;
+
+    size_t bufferSize;
+    if (type == kJSTypedArrayTypeArrayBuffer)
+        bufferSize = JSObjectGetArrayBufferByteLength(context, objectRef, exception);
+    else
+        bufferSize = JSObjectGetTypedArrayByteLength(context, objectRef, exception);
+    if (!bufferSize)
+        return false;
+
+    encoder.encodeFixedLengthData(reinterpret_cast<const uint8_t*>(buffer), bufferSize, 1);
+    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 RectType> bool encodeRectType(IPC::Encoder& encoder, JSC::JSGlobalObject* globalObject, JSC::JSObject* jsObject)
+{
+    auto& vm = globalObject->vm();
+    auto jsX = jsObject->get(globalObject, JSC::Identifier::fromString(vm, "x"_s));
+    if (!jsX.isNumber())
+        return false;
+    auto jsY = jsObject->get(globalObject, JSC::Identifier::fromString(vm, "y"_s));
+    if (!jsY.isNumber())
+        return false;
+    auto jsWidth = jsObject->get(globalObject, JSC::Identifier::fromString(vm, "width"_s));
+    if (!jsWidth.isNumber())
+        return false;
+    auto jsHeight = jsObject->get(globalObject, JSC::Identifier::fromString(vm, "height"_s));
+    if (!jsHeight.isNumber())
+        return false;
+    encoder << RectType(jsX.asNumber(), jsY.asNumber(), jsWidth.asNumber(), jsHeight.asNumber());
+    return true;
+}
+
+template<typename IntegralType> bool encodeIntegralType(IPC::Encoder& encoder, JSC::JSValue jsValue)
+{
+    if (jsValue.isBigInt()) {
+        // FIXME: Support negative BigInt.
+        auto result = JSC::JSBigInt::toUint64(jsValue);
+        if (!result)
+            return false;
+        encoder << static_cast<IntegralType>(*result);
+        return true;
+    }
+    if (!jsValue.isNumber())
+        return false;
+    double value = jsValue.asNumber();
+    encoder << static_cast<IntegralType>(value);
+    return true;
+}
+
+enum class ArrayMode { Tuple, Vector };
+static bool encodeArgument(IPC::Encoder&, JSIPC&, JSContextRef, JSValueRef, JSValueRef* exception, ArrayMode = ArrayMode::Tuple);
+
+struct VectorEncodeHelper {
+    Ref<JSIPC> jsIPC;
+    JSContextRef context;
+    JSValueRef valueRef;
+    JSValueRef* exception;
+    bool& success;
+
+    void encode(IPC::Encoder& encoder) const
+    {
+        if (!success)
+            return;
+        success = encodeArgument(encoder, jsIPC.get(), context, valueRef, exception, ArrayMode::Vector);
+    }
+};
+
+static bool encodeArgument(IPC::Encoder& encoder, JSIPC& jsIPC, JSContextRef context, JSValueRef valueRef, JSValueRef* exception, ArrayMode arrayMode)
+{
+    auto objectRef = JSValueToObject(context, valueRef, exception);
+    if (!objectRef)
+        return false;
+
+    if (auto type = JSValueGetTypedArrayType(context, objectRef, exception); type != kJSTypedArrayTypeNone)
+        return encodeTypedArray(encoder, context, objectRef, type, exception);
+
+    auto* globalObject = toJS(context);
+    auto& vm = globalObject->vm();
+    JSC::JSLockHolder lock(vm);
+    auto scope = DECLARE_CATCH_SCOPE(globalObject->vm());
+    auto* jsObject = toJS(globalObject, objectRef).getObject();
+    ASSERT(jsObject);
+    if (JSValueIsArray(context, objectRef)) {
+        auto jsLength = jsObject->get(globalObject, JSC::Identifier::fromString(vm, "length"_s));
+        if (scope.exception())
+            return false;
+        if (!jsLength.isNumber()) {
+            *exception = createTypeError(context, "Array length is not a number"_s);
+            return false;
+        }
+        auto length = jsLength.asNumber();
+
+        Vector<VectorEncodeHelper> vector;
+        bool success = true;
+        for (unsigned i = 0; i < length; ++i) {
+            auto itemRef = JSObjectGetPropertyAtIndex(context, objectRef, i, exception);
+            if (!itemRef)
+                return false;
+            vector.append(VectorEncodeHelper { jsIPC, context, itemRef, exception, success });
+        }
+        if (arrayMode == ArrayMode::Tuple) {
+            for (auto& item : vector)
+                item.encode(encoder);
+        } else
+            encoder << vector;
+        return success;
+    }
+
+    auto jsType = jsObject->get(globalObject, JSC::Identifier::fromString(vm, "type"_s));
+    if (scope.exception())
+        return false;
+
+    auto type = jsType.toWTFString(globalObject);
+    if (scope.exception())
+        return false;
+
+    if (type == "IntRect") {
+        if (!encodeRectType<WebCore::IntRect>(encoder, globalObject, jsObject)) {
+            *exception = createTypeError(context, "Failed to convert IntRect"_s);
+            return false;
+        }
+        return true;
+    }
+
+    if (type == "FloatRect") {
+        if (!encodeRectType<WebCore::FloatRect>(encoder, globalObject, jsObject)) {
+            *exception = createTypeError(context, "Failed to convert FloatRect"_s);
+            return false;
+        }
+        return true;
+    }
+
+    if (type == "FrameInfoData") {
+        auto webFrame = makeRefPtr(jsIPC.webFrame());
+        if (!webFrame) {
+            *exception = createTypeError(context, "Failed to get the frame"_s);
+            return false;
+        }
+        encoder << webFrame->info();
+        return true;
+    }
+
+    auto jsValue = jsObject->get(globalObject, JSC::Identifier::fromString(vm, "value"_s));
+    if (scope.exception())
+        return false;
+
+    if (type == "String") {
+        if (jsValue.isUndefinedOrNull()) {
+            encoder << String { };
+            return true;
+        }
+        auto string = jsValue.toWTFString(globalObject);
+        if (scope.exception())
+            return false;
+        encoder << string;
+        return true;
+    }
+
+    if (type == "RGBA") {
+        if (!jsValue.isNumber()) {
+            *exception = createTypeError(context, "RGBA value should be a number"_s);
+            return false;
+        }
+        uint32_t rgba = jsValue.asNumber();
+        encoder << WebCore::Color { WebCore::asSRGBA(WebCore::PackedColor::RGBA(rgba)) };
+        return true;
+    }
+
+    bool integralResult;
+    if (type == "bool")
+        integralResult = encodeIntegralType<bool>(encoder, jsValue);
+    else if (type == "int8_t")
+        integralResult = encodeIntegralType<int8_t>(encoder, jsValue);
+    else if (type == "int16_t")
+        integralResult = encodeIntegralType<int16_t>(encoder, jsValue);
+    else if (type == "int32_t")
+        integralResult = encodeIntegralType<int32_t>(encoder, jsValue);
+    else if (type == "int64_t")
+        integralResult = encodeIntegralType<int64_t>(encoder, jsValue);
+    else if (type == "uint8_t")
+        integralResult = encodeIntegralType<uint8_t>(encoder, jsValue);
+    else if (type == "uint16_t")
+        integralResult = encodeIntegralType<uint16_t>(encoder, jsValue);
+    else if (type == "uint32_t")
+        integralResult = encodeIntegralType<uint32_t>(encoder, jsValue);
+    else if (type == "uint64_t")
+        integralResult = encodeIntegralType<uint64_t>(encoder, jsValue);
+    else {
+        *exception = createTypeError(context, "Bad type name"_s);
+        return false;
+    }
+    if (!integralResult) {
+        *exception = createTypeError(context, "Failed to encode an integer"_s);
+        return false;
+    }
+    return true;
+}
+
+JSValueRef JSIPC::sendMessage(JSContextRef context, JSObjectRef, 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 = toRef(JSC::createTypeError(toJS(context), "Wrong type"_s));
+        return JSValueMakeUndefined(context);
+    }
+
+    if (argumentCount < 3) {
+        *exception = toRef(JSC::createTypeError(toJS(context), "Must specify the target process, desination ID, and message ID as the first three arguments"_s));
+        return JSValueMakeUndefined(context);
+    }
+
+    auto connection = processTargetFromArgument(globalObject, arguments[0], exception);
+    if (!connection)
+        return JSValueMakeUndefined(context);
+
+    auto destinationID = destinationIDFromArgument(globalObject, arguments[1], exception);
+    if (!destinationID)
+        return JSValueMakeUndefined(context);
+
+    auto messageID = messageIDFromArgument(globalObject, arguments[2], exception);
+    if (!messageID)
+        return JSValueMakeUndefined(context);
+
+    auto encoder = makeUnique<IPC::Encoder>(static_cast<IPC::MessageName>(static_cast<uint64_t>(*messageID)), *destinationID);
+
+    if (argumentCount > 3) {
+        if (!encodeArgument(*encoder, *jsIPC, context, arguments[3], exception))
+            return JSValueMakeUndefined(context);
+    }
+
+    // FIXME: Add the support for specifying IPC options.
+
+    connection->sendMessage(WTFMove(encoder), { });
+
+    return JSValueMakeUndefined(context);
+}
+
+JSValueRef JSIPC::sendSyncMessage(JSContextRef context, JSObjectRef, 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 = toRef(JSC::createTypeError(toJS(context), "Wrong type"_s));
+        return JSValueMakeUndefined(context);
+    }
+
+    if (argumentCount < 4) {
+        *exception = toRef(JSC::createTypeError(toJS(context), "Must specify the target process, desination ID, message ID, and timeout as the first four arguments"_s));
+        return JSValueMakeUndefined(context);
+    }
+
+    auto connection = processTargetFromArgument(globalObject, arguments[0], exception);
+    if (!connection)
+        return JSValueMakeUndefined(context);
+
+    auto destinationID = destinationIDFromArgument(globalObject, arguments[1], exception);
+    if (!destinationID)
+        return JSValueMakeUndefined(context);
+
+    auto messageID = messageIDFromArgument(globalObject, arguments[2], exception);
+    if (!messageID)
+        return JSValueMakeUndefined(context);
+
+    Seconds timeout;
+    {
+        auto jsValue = toJS(globalObject, arguments[3]);
+        if (!jsValue.isNumber()) {
+            *exception = toRef(JSC::createTypeError(globalObject, "timeout must be a number"_s));
+            return JSValueMakeUndefined(context);
+        }
+        timeout = Seconds { jsValue.asNumber() };
+    }
+    
+    // FIXME: Support the options.
+
+    uint64_t syncRequestID = 0;
+    auto encoder = connection->createSyncMessageEncoder(static_cast<IPC::MessageName>(static_cast<uint64_t>(*messageID)), *destinationID, syncRequestID);
+
+    if (argumentCount > 4) {
+        if (!encodeArgument(*encoder, *jsIPC, context, arguments[4], exception))
+            return JSValueMakeUndefined(context);
+    }
+
+    auto replyDecoder = connection->sendSyncMessage(syncRequestID, WTFMove(encoder), timeout, { });
+    // FIXME: Decode the reply.
+
+    return JSValueMakeUndefined(context);
+}
+
+JSValueRef JSIPC::visitedLinkStoreID(JSContextRef context, JSObjectRef thisObject, JSStringRef, JSValueRef* exception)
+{
+    return retrieveID(context, thisObject, exception, [](JSIPC& wrapped) {
+        auto webPage = makeRef(*wrapped.m_webPage);
+        return webPage->visitedLinkTableID();
+    });
+}
+
+JSValueRef JSIPC::webPageProxyID(JSContextRef context, JSObjectRef thisObject, JSStringRef, JSValueRef* exception)
+{
+    return retrieveID(context, thisObject, exception, [](JSIPC& wrapped) {
+        return wrapped.m_webPage->webPageProxyID();
+    });
+}
+
+JSValueRef JSIPC::frameIdentifier(JSContextRef context, JSObjectRef thisObject, JSStringRef, JSValueRef* exception)
+{
+    return retrieveID(context, thisObject, exception, [](JSIPC& wrapped) {
+        return wrapped.m_webFrame->frameID().toUInt64();
+    });
+}
+
+JSValueRef JSIPC::retrieveID(JSContextRef context, JSObjectRef thisObject, JSValueRef* exception, const WTF::Function<uint64_t(JSIPC&)>& callback)
+{
+    auto* globalObject = toJS(context);
+    auto& vm = globalObject->vm();
+    JSC::JSLockHolder lock(vm);
+
+    RefPtr<JSIPC> wrapped = toWrapped(context, thisObject);
+    if (!wrapped) {
+        *exception = toRef(JSC::createTypeError(toJS(context), "Wrong type"_s));
+        return JSValueMakeUndefined(context);
+    }
+
+    auto id = callback(*wrapped);
+    JSC::JSCell* jsValue = JSC::JSBigInt::createFrom(globalObject, id);
+    return toRef(vm, jsValue);
+}
+
+JSValueRef JSIPC::messages(JSContextRef context, JSObjectRef thisObject, JSStringRef, JSValueRef* exception)
+{
+    auto* globalObject = toJS(context);
+    auto& vm = globalObject->vm();
+    JSC::JSLockHolder lock(vm);
+
+    auto* impl = toWrapped(context, thisObject);
+    if (!impl) {
+        *exception = toRef(JSC::createTypeError(toJS(context), "Wrong type"_s));
+        return JSValueMakeUndefined(context);
+    }
+
+    JSC::JSObject* messagesObject = constructEmptyObject(globalObject, globalObject->objectPrototype());
+    auto nameIdent = JSC::Identifier::fromString(vm, "name");
+    for (unsigned i = 0; i < static_cast<unsigned>(IPC::MessageName::Last); ++i) {
+        auto* messageName = description(static_cast<IPC::MessageName>(i));
+
+        JSC::JSObject* dictionary = constructEmptyObject(globalObject, globalObject->objectPrototype());
+        dictionary->putDirect(vm, nameIdent, JSC::JSValue(i));
+
+        // FIXME: Add argument names.
+
+        messagesObject->putDirect(vm, JSC::Identifier::fromString(vm, messageName), dictionary);
+    }
+
+    return toRef(vm, messagesObject);
+}
+
+void inject(WebPage& webPage, WebFrame& webFrame, WebCore::DOMWrapperWorld& world)
+{
+    auto* globalObject = webFrame.coreFrame()->script().globalObject(world);
+
+    auto& vm = globalObject->vm();
+    JSC::JSLockHolder lock(vm);
+    auto scope = DECLARE_CATCH_SCOPE(vm);
+    auto wrapped = JSIPC::create(webPage, webFrame);
+    JSObjectRef wrapperObject = JSObjectMake(toGlobalRef(globalObject), JSIPC::wrapperClass(), wrapped.ptr());
+    globalObject->putDirect(vm, JSC::Identifier::fromString(vm, "IPC"), toJS(globalObject, wrapperObject));
+
+    scope.clearException();
+}
+
+} // namespace IPCTestingAPI
+
+} // namespace WebKit
+
+#endif
diff --git a/Source/WebKit/WebProcess/WebPage/IPCTestingAPI.h b/Source/WebKit/WebProcess/WebPage/IPCTestingAPI.h
new file mode 100644 (file)
index 0000000..4361aa0
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(IPC_TESTING_API)
+
+namespace WebCore {
+
+class DOMWrapperWorld;
+class Frame;
+
+};
+
+namespace WebKit {
+
+class WebFrame;
+class WebPage;
+
+namespace IPCTestingAPI {
+
+void inject(WebPage&, WebFrame&, WebCore::DOMWrapperWorld&);
+
+}
+
+} // namespace WebKit
+
+#endif
index 5785349e8ce126a587832bbb6a727e9f344d5d50..9ff66eafd33c7d09c8a7c9396ec8cbae5cd65a47 100644 (file)
@@ -63,7 +63,7 @@ class WebPage;
 struct FrameInfoData;
 struct WebsitePoliciesData;
 
-class WebFrame : public API::ObjectImpl<API::Object::Type::BundleFrame> {
+class WebFrame : public API::ObjectImpl<API::Object::Type::BundleFrame>, public CanMakeWeakPtr<WebFrame> {
 public:
     static Ref<WebFrame> create() { return adoptRef(*new WebFrame); }
     static Ref<WebFrame> createSubframe(WebPage*, const String& frameName, WebCore::HTMLFrameOwnerElement*);
index 260a0e2acb4be9ea7b3d0f3abcfd0602f51472b5..4c6a1720d74b4f2cf3c2fcb3a7ce1fe703d069f3 100644 (file)
@@ -789,6 +789,10 @@ WebPage::WebPage(PageIdentifier pageID, WebPageCreationParameters&& parameters)
         WebProcess::singleton().ensureGPUProcessConnection().updateParameters(parameters);
 #endif
 
+#if ENABLE(IPC_TESTING_API)
+    m_visitedLinkTableID = parameters.visitedLinkTableID;
+#endif
+
 #if ENABLE(VP9)
     if (parameters.shouldEnableVP9Decoder)
         WebProcess::singleton().enableVP9Decoder();
@@ -3784,6 +3788,10 @@ void WebPage::updatePreferences(const WebPreferencesStore& store)
     WebProcess::singleton().supplement<RemoteMediaPlayerManager>()->updatePreferences(settings);
     WebProcess::singleton().setUseGPUProcessForMedia(settings.useGPUProcessForMediaEnabled());
 #endif
+
+#if ENABLE(IPC_TESTING_API)
+    m_ipcTestingAPIEnabled = store.getBoolValueForKey(WebPreferencesKey::ipcTestingAPIEnabledKey());
+#endif
 }
 
 #if ENABLE(DATA_DETECTION)
index 1ff35fb0936a5c7716aa4c012d26e073b4478e95..91177c9a5908abff482d4b762439d4ace25ea4e2 100644 (file)
@@ -1339,6 +1339,12 @@ public:
 
     void updateCORSDisablingPatterns(Vector<String>&&);
 
+#if ENABLE(IPC_TESTING_API)
+    bool ipcTestingAPIEnabled() const { return m_ipcTestingAPIEnabled; }
+    uint64_t webPageProxyID() const { return messageSenderDestinationID(); }
+    uint64_t visitedLinkTableID() const { return m_visitedLinkTableID; }
+#endif
+
     void getProcessDisplayName(CompletionHandler<void(String&&)>&&);
 
     WebCore::AllowsContentJavaScript allowsContentJavaScriptFromMostRecentNavigation() const { return m_allowsContentJavaScriptFromMostRecentNavigation; }
@@ -2171,6 +2177,11 @@ private:
     bool m_canUseCredentialStorage { true };
 
     Vector<String> m_corsDisablingPatterns;
+
+#if ENABLE(IPC_TESTING_API)
+    bool m_ipcTestingAPIEnabled { false };
+    uint64_t m_visitedLinkTableID;
+#endif
 };
 
 #if !PLATFORM(IOS_FAMILY)
index 08d3308843c5378f613c548771855f58f27c9d20..b598f74a277ad8056f0b35d199c23ea9980ed280 100644 (file)
@@ -1,3 +1,16 @@
+2020-10-08  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Make it possible to send an arbitrary IPC message from JavaScript
+        https://bugs.webkit.org/show_bug.cgi?id=217423
+        <rdar://problem/69969351>
+
+        Reviewed by Geoffrey Garen.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm: Added.
+        (-[IPCTestingAPIDelegate webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:]):
+        (TEST):
+
 2020-10-08  Sam Weinig  <weinig@apple.com>
 
         Refactor TestOptions code in WebKitTestRunner to make it easier to rationalize and extend
index a7c0cc70b77909b9179c4a53c14b06e7ed56ca60..4c39ea180b5bee42344399041a5e610b64f5ae66 100644 (file)
                9B4F8FA7159D52DD002D9F94 /* HTMLCollectionNamedItem.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 9B4F8FA6159D52CA002D9F94 /* HTMLCollectionNamedItem.html */; };
                9B59F12A2034086F009E63D5 /* mso-list-compat-mode.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 9B59F12920340854009E63D5 /* mso-list-compat-mode.html */; };
                9B62630C1F8C25C8007EE29B /* copy-url.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 9B62630B1F8C2510007EE29B /* copy-url.html */; };
+               9B6D9FF9252EFDE500A51640 /* IPCTestingAPI.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9B6D9FF8252EFDE500A51640 /* IPCTestingAPI.mm */; };
                9B7A37C41F8AEBA5004AA228 /* CopyURL.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9B7A37C21F8AEBA5004AA228 /* CopyURL.mm */; };
                9B7D740F1F8378770006C432 /* paste-rtfd.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 9B7D740E1F8377E60006C432 /* paste-rtfd.html */; };
                9B9332CE2320C745002D50E8 /* cocoa-writer-markup-with-lists.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 9B9332CD2320C73E002D50E8 /* cocoa-writer-markup-with-lists.html */; };
                9B4F8FA6159D52CA002D9F94 /* HTMLCollectionNamedItem.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = HTMLCollectionNamedItem.html; sourceTree = "<group>"; };
                9B59F12920340854009E63D5 /* mso-list-compat-mode.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "mso-list-compat-mode.html"; sourceTree = "<group>"; };
                9B62630B1F8C2510007EE29B /* copy-url.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "copy-url.html"; sourceTree = "<group>"; };
+               9B6D9FF8252EFDE500A51640 /* IPCTestingAPI.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = IPCTestingAPI.mm; sourceTree = "<group>"; };
                9B79164F1BD89D0D00D50B8F /* FirstResponderScrollingPosition.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FirstResponderScrollingPosition.mm; sourceTree = "<group>"; };
                9B7A37C21F8AEBA5004AA228 /* CopyURL.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CopyURL.mm; sourceTree = "<group>"; };
                9B7D740E1F8377E60006C432 /* paste-rtfd.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "paste-rtfd.html"; sourceTree = "<group>"; };
                                CED73D35246F204C00DAE327 /* InsertTextAlternatives.mm */,
                                2DB0232E1E4E871800707123 /* InteractionDeadlockAfterCrash.mm */,
                                2D116E1223E0CB3900208900 /* iOSMouseSupport.mm */,
+                               9B6D9FF8252EFDE500A51640 /* IPCTestingAPI.mm */,
                                5C69BDD41F82A7EB000F4F4B /* JavaScriptDuringNavigation.mm */,
                                5C0160C021A132320077FA32 /* JITEnabled.mm */,
                                C25CCA051E51380B0026CB8A /* LineBreaking.mm */,
                                7A909A821D877480007E10F8 /* IntRectTests.cpp in Sources */,
                                7A909A831D877480007E10F8 /* IntSizeTests.cpp in Sources */,
                                2D116E1323E0CB3A00208900 /* iOSMouseSupport.mm in Sources */,
+                               9B6D9FF9252EFDE500A51640 /* IPCTestingAPI.mm in Sources */,
                                5C0BF8931DD599BD00B00328 /* IsNavigationActionTrusted.mm in Sources */,
                                CD5FF49F2162E943004BD86F /* ISOBox.cpp in Sources */,
                                5C69BDD51F82A7EF000F4F4B /* JavaScriptDuringNavigation.mm in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm
new file mode 100644 (file)
index 0000000..cff212d
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+
+#import "TestWKWebView.h"
+#import "Utilities.h"
+#import <WebKit/WKNavigationDelegatePrivate.h>
+#import <WebKit/WKPreferencesPrivate.h>
+#import <WebKit/WKWebView.h>
+#import <WebKit/_WKInternalDebugFeature.h>
+#import <wtf/RetainPtr.h>
+
+static bool done = false;
+static RetainPtr<NSString> alertMessage;
+
+@interface IPCTestingAPIDelegate : NSObject <WKUIDelegate>
+@end
+    
+@implementation IPCTestingAPIDelegate
+
+- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
+{
+    alertMessage = message;
+    done = true;
+    completionHandler();
+}
+
+@end
+
+TEST(IPCTestingAPI, IsDisabledByDefault)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+
+    auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]);
+    [webView setUIDelegate:delegate.get()];
+
+    done = false;
+    [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>alert(typeof(IPC));</script>"];
+    TestWebKitAPI::Util::run(&done);
+    EXPECT_STREQ([alertMessage UTF8String], "undefined");
+}
+
+#if ENABLE(IPC_TESTING_API)
+
+TEST(IPCTestingAPI, CanSendAlert)
+{
+    RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    for (_WKInternalDebugFeature *feature in [WKPreferences _internalDebugFeatures]) {
+        if ([feature.key isEqualToString:@"IPCTestingAPIEnabled"]) {
+            [[configuration preferences] _setEnabled:YES forInternalDebugFeature:feature];
+            break;
+        }
+    }
+    RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration.get()]);
+
+    auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]);
+    [webView setUIDelegate:delegate.get()];
+
+    done = false;
+    [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>IPC.sendSyncMessage('UI', IPC.webPageProxyID, IPC.messages.WebPageProxy_RunJavaScriptAlert.name, 100,"
+        "[{type: 'uint64_t', value: IPC.frameIdentifier}, {type: 'FrameInfoData'}, {'type': 'String', 'value': 'hi'}]);</script>"];
+    TestWebKitAPI::Util::run(&done);
+
+    EXPECT_STREQ([alertMessage UTF8String], "hi");
+}
+
+#endif