Using version 1 CFRunloopSource for faster task dispatch
https://bugs.webkit.org/show_bug.cgi?id=202874

Reviewed by Geoffrey Garen.

Source/WTF:

We used CFRunLoopWakeUp to wake up runloop to process source, which seems to be slow according to profiling. To
avoid calling CFRunLoopWakeUp, we should use version 1 CFRunloopSource instead of version 0. This patch brings
about 15% speedup for test PerformanceTests/IndexedDB/basic/objectstore-get.html.

* wtf/RunLoop.cpp:
(WTF::RunLoop::initializeWebRunLoop):
(WTF::RunLoop::web):
* wtf/RunLoop.h:
* wtf/cf/RunLoopCF.cpp:
(WTF::RunLoop::performWork):
(WTF::RunLoop::RunLoop):
(WTF::RunLoop::~RunLoop):
(WTF::RunLoop::wakeUp):
* wtf/cocoa/MainThreadCocoa.mm:
(WTF::initializeMainThreadPlatform):
(WTF::scheduleDispatchFunctionsOnMainThread):
(WTF::initializeWebThread):
(-[JSWTFMainThreadCaller call]): Deleted.

Tools:

Fix a flaky test.

* TestWebKitAPI/Tests/WebKit/getUserMedia.html:

LayoutTests:

Fix a flaky test.

* inspector/css/pseudo-creation-expected.txt:
* inspector/css/pseudo-creation.html:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251261 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index fd3edf1..911a3a8 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,15 @@
+2019-10-17  Sihui Liu  <sihui_liu@apple.com>
+
+        Using version 1 CFRunloopSource for faster task dispatch
+        https://bugs.webkit.org/show_bug.cgi?id=202874
+
+        Reviewed by Geoffrey Garen.
+
+        Fix a flaky test.
+
+        * inspector/css/pseudo-creation-expected.txt:
+        * inspector/css/pseudo-creation.html:
+
 2019-10-17  Ryosuke Niwa  <rniwa@webkit.org>
 
         Make requestIdleCallback suspendable
diff --git a/LayoutTests/inspector/css/pseudo-creation-expected.txt b/LayoutTests/inspector/css/pseudo-creation-expected.txt
index 5b83233..49b7ffa 100644
--- a/LayoutTests/inspector/css/pseudo-creation-expected.txt
+++ b/LayoutTests/inspector/css/pseudo-creation-expected.txt
@@ -12,11 +12,11 @@
 
 Calling "createElementWithClass("test-pseudo-with-content")"...
 Checking for nodes with class ".test-pseudo-with-content"...
-PASS: Created ::before pseudo element
 PASS: There should be 1 node with the class ".test-pseudo-with-content".
+PASS: Created ::before pseudo element
 
 Calling "removeElementWithClass("test-pseudo-with-content")"...
 Checking for nodes with class ".test-pseudo-with-content"...
-PASS: Removed ::before pseudo element
 PASS: There should be 0 node with the class ".test-pseudo-with-content".
+PASS: Removed ::before pseudo element
 
diff --git a/LayoutTests/inspector/css/pseudo-creation.html b/LayoutTests/inspector/css/pseudo-creation.html
index a01ce0d..84f5050 100644
--- a/LayoutTests/inspector/css/pseudo-creation.html
+++ b/LayoutTests/inspector/css/pseudo-creation.html
@@ -26,7 +26,8 @@
 
 function test() {
     let documentNode = null;
-    let pseudoElement = null;
+    let pseudoElementAdded = null;
+    let pseudoElementRemoved = null;
 
     function handlePromiseReject(error) {
         console.log(error);
@@ -56,27 +57,37 @@
         .catch(handlePromiseReject);
     }
 
-    function createElementWithClass(className) {
+    function createElementWithClass(className, shouldCheckElement) {
         return evaluateWithLog(`createElementWithClass("${className}")`)
         .then(() => checkElementsWithClass(className, 1))
+        .then(() => {
+            if (shouldCheckElement) {
+                if (pseudoElementAdded)
+                    ProtocolTest.pass(`Created ::${pseudoElementAdded.pseudoType} pseudo element`);
+                else
+                    return pseudoElementAddedPromise.then(() => { ProtocolTest.pass(`Created ::${pseudoElementAdded.pseudoType} pseudo element`); });
+            }
+        })
         .catch(handlePromiseReject);
     }
 
-    function removeElementWithClass(className) {
+    function removeElementWithClass(className, shouldCheckElement) {
         return evaluateWithLog(`removeElementWithClass("${className}")`)
         .then(() => checkElementsWithClass(className, 0))
+        .then(() => {
+            if (shouldCheckElement) {
+                if (pseudoElementRemoved)
+                    ProtocolTest.expectEqual(pseudoElementRemoved.pseudoElementId, pseudoElementAdded.nodeId, `Removed ::${pseudoElementAdded.pseudoType} pseudo element`);
+                else
+                    return pseudoElementRemovedPromise.then(() => { ProtocolTest.expectEqual(pseudoElementRemoved.pseudoElementId, pseudoElementAdded.nodeId, `Removed ::${pseudoElementAdded.pseudoType} pseudo element`); });
+            }
+        })
         .catch(handlePromiseReject);
     }
 
-    InspectorProtocol.eventHandler["DOM.pseudoElementAdded"] = (response) => {
-        pseudoElement = response.params.pseudoElement;
+    let pseudoElementAddedPromise = InspectorProtocol.awaitEvent({event: "DOM.pseudoElementAdded"}).then((event) => { pseudoElementAdded = event.params.pseudoElement});
+    let pseudoElementRemovedPromise = InspectorProtocol.awaitEvent({event: "DOM.pseudoElementRemoved"}).then((event) => { pseudoElementRemoved = event.params});
 
-        ProtocolTest.pass(`Created ::${pseudoElement.pseudoType} pseudo element`);
-    };
-
-    InspectorProtocol.eventHandler["DOM.pseudoElementRemoved"] = (response) => {
-        ProtocolTest.expectEqual(response.params.pseudoElementId, pseudoElement.nodeId, `Removed ::${pseudoElement.pseudoType} pseudo element`);
-    };
 
     ProtocolTest.log("Requesting document...");
     InspectorProtocol.sendCommand("DOM.getDocument", {}, (response) => {
@@ -85,10 +96,10 @@
         documentNode = response.result.root;
 
         Promise.resolve()
-        .then(() => createElementWithClass("test-pseudo-without-content"))
-        .then(() => removeElementWithClass("test-pseudo-without-content"))
-        .then(() => createElementWithClass("test-pseudo-with-content"))
-        .then(() => removeElementWithClass("test-pseudo-with-content"))
+        .then(() => createElementWithClass("test-pseudo-without-content"), false)
+        .then(() => removeElementWithClass("test-pseudo-without-content"), false)
+        .then(() => createElementWithClass("test-pseudo-with-content", true))
+        .then(() => removeElementWithClass("test-pseudo-with-content", true))
         .then(() => ProtocolTest.completeTest())
         .catch(handlePromiseReject);
     });
diff --git a/Source/WTF/ChangeLog b/Source/WTF/ChangeLog
index 7555d4e..0e44522 100644
--- a/Source/WTF/ChangeLog
+++ b/Source/WTF/ChangeLog
@@ -1,3 +1,29 @@
+2019-10-17  Sihui Liu  <sihui_liu@apple.com>
+
+        Using version 1 CFRunloopSource for faster task dispatch
+        https://bugs.webkit.org/show_bug.cgi?id=202874
+
+        Reviewed by Geoffrey Garen.
+
+        We used CFRunLoopWakeUp to wake up runloop to process source, which seems to be slow according to profiling. To 
+        avoid calling CFRunLoopWakeUp, we should use version 1 CFRunloopSource instead of version 0. This patch brings
+        about 15% speedup for test PerformanceTests/IndexedDB/basic/objectstore-get.html.
+
+        * wtf/RunLoop.cpp:
+        (WTF::RunLoop::initializeWebRunLoop):
+        (WTF::RunLoop::web):
+        * wtf/RunLoop.h:
+        * wtf/cf/RunLoopCF.cpp:
+        (WTF::RunLoop::performWork):
+        (WTF::RunLoop::RunLoop):
+        (WTF::RunLoop::~RunLoop):
+        (WTF::RunLoop::wakeUp):
+        * wtf/cocoa/MainThreadCocoa.mm:
+        (WTF::initializeMainThreadPlatform):
+        (WTF::scheduleDispatchFunctionsOnMainThread):
+        (WTF::initializeWebThread):
+        (-[JSWTFMainThreadCaller call]): Deleted.
+
 2019-10-16  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         Unreviewed, fix the internal macOS 10.13 and 10.14 builds after r251171
diff --git a/Source/WTF/wtf/RunLoop.cpp b/Source/WTF/wtf/RunLoop.cpp
index 1be22d8..938b9e9 100644
--- a/Source/WTF/wtf/RunLoop.cpp
+++ b/Source/WTF/wtf/RunLoop.cpp
@@ -33,6 +33,9 @@
 namespace WTF {
 
 static RunLoop* s_mainRunLoop;
+#if USE(WEB_THREAD)
+static RunLoop* s_webRunLoop;
+#endif
 
 // Helper class for ThreadSpecificData.
 class RunLoop::Holder {
@@ -69,6 +72,19 @@
     return *s_mainRunLoop;
 }
 
+#if USE(WEB_THREAD)
+void RunLoop::initializeWebRunLoop()
+{
+    s_webRunLoop = &RunLoop::current();
+}
+
+RunLoop& RunLoop::web()
+{
+    ASSERT(s_webRunLoop);
+    return *s_webRunLoop;
+}
+#endif
+
 bool RunLoop::isMain()
 {
     ASSERT(s_mainRunLoop);
diff --git a/Source/WTF/wtf/RunLoop.h b/Source/WTF/wtf/RunLoop.h
index 9de8859..a85118d 100644
--- a/Source/WTF/wtf/RunLoop.h
+++ b/Source/WTF/wtf/RunLoop.h
@@ -49,9 +49,15 @@
     // Must be called from the main thread (except for the Mac platform, where it
     // can be called from any thread).
     WTF_EXPORT_PRIVATE static void initializeMainRunLoop();
+#if USE(WEB_THREAD)
+    WTF_EXPORT_PRIVATE static void initializeWebRunLoop();
+#endif
 
     WTF_EXPORT_PRIVATE static RunLoop& current();
     WTF_EXPORT_PRIVATE static RunLoop& main();
+#if USE(WEB_THREAD)
+    WTF_EXPORT_PRIVATE static RunLoop& web();
+#endif
     WTF_EXPORT_PRIVATE static bool isMain();
     ~RunLoop();
 
@@ -178,9 +184,10 @@
 
     Lock m_loopLock;
 #elif USE(COCOA_EVENT_LOOP)
-    static void performWork(void*);
+    static void performWork(CFMachPortRef, void* msg, CFIndex size, void* info);
     RetainPtr<CFRunLoopRef> m_runLoop;
     RetainPtr<CFRunLoopSourceRef> m_runLoopSource;
+    RetainPtr<CFMachPortRef> m_port;
 #elif USE(GLIB_EVENT_LOOP)
     GRefPtr<GMainContext> m_mainContext;
     Vector<GRefPtr<GMainLoop>> m_mainLoops;
diff --git a/Source/WTF/wtf/cf/RunLoopCF.cpp b/Source/WTF/wtf/cf/RunLoopCF.cpp
index 27d3fe2..b2e2e5b 100644
--- a/Source/WTF/wtf/cf/RunLoopCF.cpp
+++ b/Source/WTF/wtf/cf/RunLoopCF.cpp
@@ -28,26 +28,29 @@
 
 #include <CoreFoundation/CoreFoundation.h>
 #include <dispatch/dispatch.h>
+#include <mach/mach.h>
 #include <wtf/AutodrainedPool.h>
 
 namespace WTF {
 
-void RunLoop::performWork(void* context)
+void RunLoop::performWork(CFMachPortRef, void*, CFIndex, void* info)
 {
     AutodrainedPool pool;
-    static_cast<RunLoop*>(context)->performWork();
+    static_cast<RunLoop*>(info)->performWork();
 }
 
 RunLoop::RunLoop()
     : m_runLoop(CFRunLoopGetCurrent())
 {
-    CFRunLoopSourceContext context = { 0, this, 0, 0, 0, 0, 0, 0, 0, performWork };
-    m_runLoopSource = adoptCF(CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context));
+    CFMachPortContext context = { 0, this, nullptr, nullptr, nullptr };
+    m_port = adoptCF(CFMachPortCreate(kCFAllocatorDefault, performWork, &context, nullptr));
+    m_runLoopSource = adoptCF(CFMachPortCreateRunLoopSource(kCFAllocatorDefault, m_port.get(), 0));
     CFRunLoopAddSource(m_runLoop.get(), m_runLoopSource.get(), kCFRunLoopCommonModes);
 }
 
 RunLoop::~RunLoop()
 {
+    CFMachPortInvalidate(m_port.get());
     CFRunLoopSourceInvalidate(m_runLoopSource.get());
 }
 
@@ -58,8 +61,16 @@
 
 void RunLoop::wakeUp()
 {
-    CFRunLoopSourceSignal(m_runLoopSource.get());
-    CFRunLoopWakeUp(m_runLoop.get());
+    mach_msg_header_t header;
+    header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
+    header.msgh_size = sizeof(mach_msg_header_t);
+    header.msgh_remote_port = CFMachPortGetPort(m_port.get());
+    header.msgh_local_port = MACH_PORT_NULL;
+    header.msgh_id = 0;
+    mach_msg_return_t result = mach_msg(&header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, header.msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
+    RELEASE_ASSERT(result == MACH_MSG_SUCCESS || result == MACH_SEND_TIMED_OUT);
+    if (result == MACH_SEND_TIMED_OUT)
+        mach_msg_destroy(&header);
 }
 
 RunLoop::CycleResult RunLoop::cycle(const String& mode)
diff --git a/Source/WTF/wtf/cocoa/MainThreadCocoa.mm b/Source/WTF/wtf/cocoa/MainThreadCocoa.mm
index 27eb00d..270b7b0 100644
--- a/Source/WTF/wtf/cocoa/MainThreadCocoa.mm
+++ b/Source/WTF/wtf/cocoa/MainThreadCocoa.mm
@@ -36,6 +36,7 @@
 #import <wtf/Assertions.h>
 #import <wtf/HashSet.h>
 #import <wtf/RetainPtr.h>
+#import <wtf/RunLoop.h>
 #import <wtf/SchedulePair.h>
 #import <wtf/Threading.h>
 
@@ -43,19 +44,6 @@
 #include <wtf/ios/WebCoreThread.h>
 #endif
 
-@interface JSWTFMainThreadCaller : NSObject
-- (void)call;
-@end
-
-@implementation JSWTFMainThreadCaller
-
-- (void)call
-{
-    WTF::dispatchFunctionsFromMainThread();
-}
-
-@end
-
 #define LOG_CHANNEL_PREFIX Log
 
 namespace WTF {
@@ -66,8 +54,6 @@
 WTFLogChannel LogThreading = { WTFLogChannelState::On, "Threading", WTFLogLevel::Error, LOG_CHANNEL_WEBKIT_SUBSYSTEM, OS_LOG_DEFAULT };
 #endif
 
-
-static JSWTFMainThreadCaller* staticMainThreadCaller;
 static bool isTimerPosted; // This is only accessed on the main thread.
 
 #if USE(WEB_THREAD)
@@ -84,9 +70,6 @@
     if (!pthread_main_np())
         RELEASE_LOG_FAULT(Threading, "WebKit Threading Violation - initial use of WebKit from a secondary thread.");
     ASSERT(pthread_main_np());
-
-    ASSERT(!staticMainThreadCaller);
-    staticMainThreadCaller = [[JSWTFMainThreadCaller alloc] init];
 }
 
 static void timerFired(CFRunLoopTimerRef timer, void*)
@@ -112,8 +95,6 @@
 
 void scheduleDispatchFunctionsOnMainThread()
 {
-    ASSERT(staticMainThreadCaller);
-    
 #if USE(WEB_THREAD)
     if (isWebThread()) {
         postTimer();
@@ -121,7 +102,9 @@
     }
 
     if (mainThreadPthread) {
-        [staticMainThreadCaller performSelector:@selector(call) onThread:mainThreadNSThread withObject:nil waitUntilDone:NO];
+        RunLoop::web().dispatch([] {
+            WTF::dispatchFunctionsFromMainThread();
+        });
         return;
     }
 #else
@@ -131,7 +114,9 @@
     }
 #endif
 
-    [staticMainThreadCaller performSelectorOnMainThread:@selector(call) withObject:nil waitUntilDone:NO];
+    RunLoop::main().dispatch([] {
+        WTF::dispatchFunctionsFromMainThread();
+    });
 }
 
 void dispatchAsyncOnMainThreadWithWebThreadLockIfNeeded(void (^block)())
@@ -196,6 +181,7 @@
         mainThreadPthread = pthread_self();
         mainThreadNSThread = [NSThread currentThread];
         sWebThread = &Thread::current();
+        RunLoop::initializeWebRunLoop();
     });
 }
 
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index 64aaa6d..49e847e 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -1,3 +1,14 @@
+2019-10-17  Sihui Liu  <sihui_liu@apple.com>
+
+        Using version 1 CFRunloopSource for faster task dispatch
+        https://bugs.webkit.org/show_bug.cgi?id=202874
+
+        Reviewed by Geoffrey Garen.
+
+        Fix a flaky test.
+
+        * TestWebKitAPI/Tests/WebKit/getUserMedia.html:
+
 2019-10-17  Jonathan Bedard  <jbedard@apple.com>
 
         Python 3: Add support in webkitpy.common.net.buildbot 
diff --git a/Tools/TestWebKitAPI/Tests/WebKit/getUserMedia.html b/Tools/TestWebKitAPI/Tests/WebKit/getUserMedia.html
index cb32d80..7e4617b 100644
--- a/Tools/TestWebKitAPI/Tests/WebKit/getUserMedia.html
+++ b/Tools/TestWebKitAPI/Tests/WebKit/getUserMedia.html
@@ -2,14 +2,15 @@
 <html>
     <head>
         <script>
-
-            let stream = null;
+            let streamPromise = Promise.resolve();
 
             function promptForCapture()
             {
-                navigator.mediaDevices.enumerateDevices().then(() => {
+                streamPromise = navigator.mediaDevices.enumerateDevices().then(() => {
                     return navigator.mediaDevices.getUserMedia({ audio: false, video: true })
-                }).then((s) => {
+                });
+
+                streamPromise.then((stream) => {
                     stream = s;
                     video.srcObject = stream;
                     console.log("Got user media");
@@ -18,23 +19,29 @@
 
             function stop(kind)
             {
-                let activeTracks = [];
-                stream.getTracks().forEach(track => {
-                    if (!kind || track.kind == kind)
-                        track.stop();
-                    else
-                        activeTracks.push(track);
+                streamPromise.then((stream) => {
+                    let activeTracks = [];
+                    stream.getTracks().forEach(track => {
+                        if (!kind || track.kind == kind)
+                            track.stop();
+                        else
+                            activeTracks.push(track);
+                    });
+ 
+                    if (!activeTracks.length) {
+                        streamPromiseDidResolve = false;
+                        video.srcObject = null;
+                    }
                 });
-
-                if (!activeTracks.length) {
-                    stream = null;
-                    video.srcObject = null;
-                }
             }
 
+            let streamPromiseDidResolve = false;
+
             function haveStream()
             {
-                return stream !== null;
+                // Our caller polls repeatedly until our promise resolves.
+                streamPromise.then((stream) => streamPromiseDidResolve = !!stream);
+                return streamPromiseDidResolve;
             }
 
             function doMultipleGetUserMediaSynchronously()
@@ -53,12 +60,12 @@
 
             function captureAudio()
             {
-                navigator.mediaDevices.getUserMedia({audio: true}).then(s => stream = s);
+                streamPromise = navigator.mediaDevices.getUserMedia({audio: true});
             }
 
             function captureAudioAndVideo()
             {
-                navigator.mediaDevices.getUserMedia({audio: true, video: true}).then(s => stream = s);
+                streamPromise = navigator.mediaDevices.getUserMedia({audio: true, video: true});
             }
         </script>
     <head>