-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Begin using C++ coroutines for WebPageProxy::decidePolicyForNavigatio…
…nAction https://bugs.webkit.org/show_bug.cgi?id=267827 Reviewed by NOBODY (OOPS!). WebKit has a long history with C++ and asynchrony. Deciding whether to proceed with a navigation has always had the ability to be done asynchronously. Before C++11, we made our own object that contained state and context for a call to indicate continuation. With https://commits.webkit.org/193766@main we finished the transition from this object, WebCore::PolicyCallback, to C++ lambdas. Since then, a large and growing amount of our code has developed the ability to do things asynchronously using WTF::CompletionHandler, a std::function-like object that contains state and context to use when continuing. Also since then, C++ has added coroutine support in C++20, including the co_await keyword. This has the potential to allow us to more elegantly write code that does many things asynchronously. WebPageProxy::decidePolicyForNavigationAction has a growing amount of complexity, with deeply nested lambdas and many functions calling another function to continue the logic, trying to break up the logic. With site isolation, I've needed to add many things to this already complex area of the code, and more are needed still. I've had difficulty passing parameters from one end of the flow to the other, through the many nested lambdas. This has led me to do introduce things like ProvisionalPageProxy's needsCookieAccessAddedInNetworkProcess, where I set a bool early in the flow and query the bool much later in the flow. This needs a better solution. In order to begin using C++ coroutines, we need a way to get into a coroutine from a function with a CompletionHandler parameter, and we also need a way to get from a coroutine to a function with a CompletionHandler parameter and await its result. For these two operations, I introduce CoroutineCaller and AwaitableTaskWithCompletionHandler. Task is the object that an asynchronous coroutine returns. Lazy<T> is what a coroutine returns for its resulting T to be awaitable, analogous to a CompletionHandler<void(T)> which is called with the resulting T. These primitives are used to make WebPageProxy::decidePolicyForNavigationAction an asynchronous coroutine. The introduction of this new technology in such a small scope makes it look like most of the code is just the CoroutineCaller/AwaitableTaskWithCompletionHandler borders to call to and from existing code, but as coroutine adoption increases we will see simpler and simpler code, where we can easily and elegantly add new steps in the logic flow by just awaiting another asynchronous step or calling a synchronous step directly. * Source/WebKit/Platform/CoroutineUtilities.h: Added. (WebKit::CoroutineHandle::CoroutineHandle): (WebKit::CoroutineHandle::operator=): (WebKit::CoroutineHandle::~CoroutineHandle): (WebKit::CoroutineHandle::handle const): (WebKit::Lazy::promise_type::final_awaitable::await_suspend): (WebKit::Lazy::promise_type::initial_suspend): (WebKit::Lazy::promise_type::get_return_object): (WebKit::Lazy::promise_type::unhandled_exception): (WebKit::Lazy::promise_type::return_value): (WebKit::Lazy::promise_type::result): (WebKit::Lazy::promise_type::setHandle): (WebKit::Lazy::promise_type::handle): (WebKit::Lazy::Awaitable::Awaitable): (WebKit::Lazy::Awaitable::await_ready const): (WebKit::Lazy::Awaitable::await_suspend): (WebKit::Lazy::Awaitable::await_resume): (WebKit::Lazy::Lazy): (WebKit::Lazy::operator co_await const): (WebKit::Task::promise_type::get_return_object): (WebKit::Task::promise_type::initial_suspend): (WebKit::Task::promise_type::unhandled_exception): (WebKit::Task::promise_type::return_void): (WebKit::CoroutineCaller::setCoroutine): (WebKit::AwaitableTaskWithCompletionHandler::AwaitableTaskWithCompletionHandler): (WebKit::AwaitableTaskWithCompletionHandler::await_ready): (WebKit::AwaitableTaskWithCompletionHandler::await_suspend): (WebKit::AwaitableTaskWithCompletionHandler::await_resume): * Source/WebKit/UIProcess/WebPageProxy.cpp: (WebKit::WebPageProxy::awaitableDecidePolicyForNavigationAction): (WebKit::WebPageProxy::decidePolicyForNavigationAction): (WebKit::WebPageProxy::continueDecidePolicyForNavigationAction): * Source/WebKit/UIProcess/WebPageProxy.h: * Source/WebKit/WebKit.xcodeproj/project.pbxproj:
- Loading branch information
1 parent
05bdebf
commit 09ea55c
Showing
5 changed files
with
209 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
/* | ||
* Copyright (C) 2024 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 | ||
|
||
#include <wtf/CompletionHandler.h> | ||
|
||
#if __has_include(<coroutine>) | ||
#include <coroutine> | ||
#else | ||
#include <experimental/coroutine> | ||
namespace std { | ||
using std::experimental::coroutine_handle; | ||
using std::experimental::suspend_never; | ||
using std::experimental::suspend_always; | ||
} | ||
#endif | ||
|
||
namespace WebKit { | ||
|
||
template<typename PromiseType> | ||
class CoroutineHandle { | ||
public: | ||
CoroutineHandle() = default; | ||
CoroutineHandle(std::coroutine_handle<PromiseType>&& handle) | ||
: m_handle(std::exchange(handle, nullptr)) { } | ||
CoroutineHandle(CoroutineHandle&& other) | ||
: m_handle(std::exchange(other.m_handle, nullptr)) { } | ||
CoroutineHandle& operator=(CoroutineHandle&& other) | ||
{ | ||
m_handle = std::exchange(other.m_handle, nullptr); | ||
return *this; | ||
} | ||
CoroutineHandle(const CoroutineHandle&) = delete; | ||
CoroutineHandle& operator=(const CoroutineHandle&) = delete; | ||
~CoroutineHandle() | ||
{ | ||
if (m_handle) | ||
m_handle.destroy(); | ||
} | ||
|
||
std::coroutine_handle<PromiseType> handle() const { return m_handle; } | ||
private: | ||
std::coroutine_handle<PromiseType> m_handle; | ||
}; | ||
|
||
// Name based on https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1056r1.html | ||
template<typename T> | ||
class Lazy { | ||
public: | ||
class promise_type { | ||
public: | ||
struct final_awaitable { | ||
bool await_ready() const noexcept { return false; } | ||
std::coroutine_handle<> await_suspend(std::coroutine_handle<promise_type> coroutine) noexcept { return coroutine.promise().handle(); } | ||
void await_resume() noexcept { } | ||
}; | ||
std::suspend_always initial_suspend() { return { }; } | ||
final_awaitable final_suspend() noexcept { return { }; } | ||
Lazy<T> get_return_object() { return Lazy<T>{ std::coroutine_handle<promise_type>::from_promise(*this) }; } | ||
void unhandled_exception() { } | ||
void return_value(T&& value) { m_value = WTFMove(value); } | ||
T result() && { return std::exchange(m_value, { }); } | ||
void setHandle(std::coroutine_handle<> handle) { m_handle = handle; } | ||
std::coroutine_handle<> handle() { return m_handle; } | ||
private: | ||
std::coroutine_handle<> m_handle; | ||
T m_value; | ||
}; | ||
class Awaitable { | ||
public: | ||
Awaitable(std::coroutine_handle<promise_type> coroutine) | ||
: m_coroutine(coroutine) { } | ||
bool await_ready() const { return m_coroutine.done(); } | ||
std::coroutine_handle<> await_suspend(std::coroutine_handle<> handle) | ||
{ | ||
m_coroutine.promise().setHandle(handle); | ||
return m_coroutine; | ||
} | ||
T await_resume() { return WTFMove(this->m_coroutine.promise()).result(); } | ||
private: | ||
std::coroutine_handle<promise_type> m_coroutine; | ||
}; | ||
Lazy(std::coroutine_handle<promise_type> coroutine) | ||
: m_coroutine(std::exchange(coroutine, nullptr)) { } | ||
Awaitable operator co_await() const { return { m_coroutine.handle() }; } | ||
private: | ||
CoroutineHandle<promise_type> m_coroutine; | ||
}; | ||
|
||
struct Task { | ||
struct promise_type { | ||
Task get_return_object() { return { std::coroutine_handle<promise_type>::from_promise(*this) }; } | ||
std::suspend_never initial_suspend() { return { }; } | ||
std::suspend_always final_suspend() noexcept { return { }; } | ||
void unhandled_exception() { } | ||
void return_void() { } | ||
}; | ||
std::coroutine_handle<promise_type> handle; | ||
}; | ||
|
||
class CoroutineCaller { | ||
WTF_MAKE_FAST_ALLOCATED; | ||
public: | ||
CoroutineCaller() = default; | ||
void setCoroutine(Function<Task()>&& coroutine) | ||
{ | ||
m_coroutine = std::exchange(coroutine, { }); | ||
m_coroutine(); | ||
} | ||
private: | ||
Function<Task()> m_coroutine; | ||
}; | ||
|
||
template<typename T> | ||
class AwaitableTaskWithCompletionHandler { | ||
public: | ||
using Task = CompletionHandler<void(CompletionHandler<void(T&&)>&&)>; | ||
AwaitableTaskWithCompletionHandler(Task&& task) | ||
: m_task(WTFMove(task)) { } | ||
bool await_ready() { return !!m_result; } | ||
void await_suspend(std::coroutine_handle<> handle) | ||
{ | ||
m_task([this, handle] (T&& result) mutable { | ||
m_result = WTFMove(result); | ||
handle(); | ||
}); | ||
} | ||
T await_resume() { return *std::exchange(m_result, { }); } | ||
private: | ||
Task m_task; | ||
std::optional<T> m_result; | ||
}; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters