Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Begin using C++ coroutines for WebPageProxy::decidePolicyForNavigationAction #23021

Conversation

achristensen07
Copy link
Contributor

@achristensen07 achristensen07 commented Jan 21, 2024

e90f899

Begin using C++ coroutines for WebPageProxy::decidePolicyForNavigationAction
https://bugs.webkit.org/show_bug.cgi?id=267827

Reviewed by Abrar Rahman Protyasha.

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::PromiseBase::initial_suspend):
(WebKit::Lazy::PromiseBase::unhandled_exception):
(WebKit::Lazy::PromiseBase::setHandle):
(WebKit::Lazy::PromiseBase::handle):
(WebKit::Lazy::Promise<void>::get_return_object):
(WebKit::Lazy::Promise<void>::return_void):
(WebKit::Lazy::Promise<void>::result):
(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::callCoroutine):
(WebKit::AwaitableTaskWithCompletionHandler::AwaitableTaskWithCompletionHandler):
(WebKit::AwaitableTaskWithCompletionHandler::await_ready):
(WebKit::AwaitableTaskWithCompletionHandler::await_suspend):
(WebKit::AwaitableTaskWithCompletionHandler::await_resume):
* Source/WebKit/Platform/IPC/Connection.h:
* Source/WebKit/UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::decidePolicyForNavigationAction):
(WebKit::WebPageProxy::awaitableDecidePolicyForNavigationAction):
(WebKit::WebPageProxy::continueDecidePolicyForNavigationAction):
* Source/WebKit/UIProcess/WebPageProxy.h:
* Source/WebKit/WebKit.xcodeproj/project.pbxproj:

Canonical link: https://commits.webkit.org/278995@main

46de710

Misc iOS, tvOS & watchOS macOS Linux Windows
❌ πŸ§ͺ style βœ… πŸ›  ios βœ… πŸ›  mac βœ… πŸ›  wpe βœ… πŸ›  wincairo
βœ… πŸ§ͺ bindings βœ… πŸ›  ios-sim βœ… πŸ›  mac-AS-debug βœ… πŸ§ͺ wpe-wk2 βœ… πŸ§ͺ wincairo-tests
βœ… πŸ§ͺ webkitperl βœ… πŸ§ͺ ios-wk2 βœ… πŸ§ͺ api-mac βœ… πŸ§ͺ api-wpe
βœ… πŸ§ͺ ios-wk2-wpt βœ… πŸ§ͺ mac-wk1 βœ… πŸ›  wpe-cairo
βœ… πŸ§ͺ api-ios βœ… πŸ§ͺ mac-wk2 βœ… πŸ›  gtk
βœ… πŸ›  tv βœ… πŸ§ͺ mac-AS-debug-wk2 βœ… πŸ§ͺ gtk-wk2
βœ… πŸ›  tv-sim βœ… πŸ§ͺ mac-wk2-stress βœ… πŸ§ͺ api-gtk
βœ… πŸ›  watch
βœ… πŸ›  πŸ§ͺ unsafe-merge βœ… πŸ›  watch-sim

@achristensen07 achristensen07 self-assigned this Jan 21, 2024
@achristensen07 achristensen07 added the WebKit Process Model Bugs related to WebKit's multi-process architecture label Jan 21, 2024
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Jan 21, 2024
@achristensen07 achristensen07 removed the merging-blocked Applied to prevent a change from being merged label Jan 22, 2024
@achristensen07 achristensen07 force-pushed the eng/Begin-using-C-coroutines-for-WebPageProxydecidePolicyForNavigationAction branch from 56710cd to 2e44d1a Compare January 22, 2024 19:59
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Jan 22, 2024
@achristensen07 achristensen07 removed the merging-blocked Applied to prevent a change from being merged label Jan 22, 2024
@achristensen07 achristensen07 force-pushed the eng/Begin-using-C-coroutines-for-WebPageProxydecidePolicyForNavigationAction branch from 2e44d1a to abec41a Compare January 22, 2024 21:33
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Jan 22, 2024
@achristensen07 achristensen07 removed the merging-blocked Applied to prevent a change from being merged label Jan 23, 2024
@achristensen07 achristensen07 force-pushed the eng/Begin-using-C-coroutines-for-WebPageProxydecidePolicyForNavigationAction branch from abec41a to 09ea55c Compare January 23, 2024 21:37
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Jan 24, 2024
@achristensen07 achristensen07 removed the merging-blocked Applied to prevent a change from being merged label May 17, 2024
@achristensen07 achristensen07 force-pushed the eng/Begin-using-C-coroutines-for-WebPageProxydecidePolicyForNavigationAction branch from 09ea55c to ed3d5ee Compare May 17, 2024 05:03
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label May 17, 2024
@achristensen07 achristensen07 removed the merging-blocked Applied to prevent a change from being merged label May 17, 2024
@achristensen07 achristensen07 force-pushed the eng/Begin-using-C-coroutines-for-WebPageProxydecidePolicyForNavigationAction branch from ed3d5ee to 4798912 Compare May 17, 2024 15:02
@achristensen07 achristensen07 force-pushed the eng/Begin-using-C-coroutines-for-WebPageProxydecidePolicyForNavigationAction branch from 4798912 to 1a8f831 Compare May 17, 2024 21:32
@achristensen07 achristensen07 force-pushed the eng/Begin-using-C-coroutines-for-WebPageProxydecidePolicyForNavigationAction branch from 1a8f831 to 3c93b0e Compare May 18, 2024 00:12
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label May 18, 2024
@@ -7074,6 +7088,15 @@ void WebPageProxy::decidePolicyForNavigationAction(Ref<WebProcessProxy>&& proces
sendCachedLinkDecorationFilteringData();
#endif

transaction = std::nullopt;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is clearly not ratified in the style guide, but I think we're leaning towards { } instead of std::nullopt nowadays.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's definitely debatable. 832 vs 1362 instances of each.

Comment on lines 30 to 40
#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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is <coroutine> not part of all toolchains we build against? Is this for macOS downlevels or for the ports using GCC?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In either case, maybe add a FIXME to simplify this when it's unnecessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed for some old macOS still. Something similar is done in HTTPServer.h. We can remove it at some point, you're right.

@achristensen07 achristensen07 removed the merging-blocked Applied to prevent a change from being merged label May 18, 2024
@achristensen07 achristensen07 force-pushed the eng/Begin-using-C-coroutines-for-WebPageProxydecidePolicyForNavigationAction branch from 3c93b0e to 46de710 Compare May 18, 2024 21:54
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label May 19, 2024
@achristensen07 achristensen07 added unsafe-merge-queue Applied to send a pull request to merge-queue, but skip building and testing and removed merging-blocked Applied to prevent a change from being merged labels May 20, 2024
…nAction

https://bugs.webkit.org/show_bug.cgi?id=267827

Reviewed by Abrar Rahman Protyasha.

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::PromiseBase::initial_suspend):
(WebKit::Lazy::PromiseBase::unhandled_exception):
(WebKit::Lazy::PromiseBase::setHandle):
(WebKit::Lazy::PromiseBase::handle):
(WebKit::Lazy::Promise<void>::get_return_object):
(WebKit::Lazy::Promise<void>::return_void):
(WebKit::Lazy::Promise<void>::result):
(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::callCoroutine):
(WebKit::AwaitableTaskWithCompletionHandler::AwaitableTaskWithCompletionHandler):
(WebKit::AwaitableTaskWithCompletionHandler::await_ready):
(WebKit::AwaitableTaskWithCompletionHandler::await_suspend):
(WebKit::AwaitableTaskWithCompletionHandler::await_resume):
* Source/WebKit/Platform/IPC/Connection.h:
* Source/WebKit/UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::decidePolicyForNavigationAction):
(WebKit::WebPageProxy::awaitableDecidePolicyForNavigationAction):
(WebKit::WebPageProxy::continueDecidePolicyForNavigationAction):
* Source/WebKit/UIProcess/WebPageProxy.h:
* Source/WebKit/WebKit.xcodeproj/project.pbxproj:

Canonical link: https://commits.webkit.org/278995@main
@webkit-commit-queue webkit-commit-queue force-pushed the eng/Begin-using-C-coroutines-for-WebPageProxydecidePolicyForNavigationAction branch from 46de710 to e90f899 Compare May 20, 2024 15:16
@webkit-commit-queue
Copy link
Collaborator

Committed 278995@main (e90f899): https://commits.webkit.org/278995@main

Reviewed commits have been landed. Closing PR #23021 and removing active labels.

@webkit-commit-queue webkit-commit-queue removed the unsafe-merge-queue Applied to send a pull request to merge-queue, but skip building and testing label May 20, 2024
@webkit-commit-queue webkit-commit-queue merged commit e90f899 into WebKit:main May 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
WebKit Process Model Bugs related to WebKit's multi-process architecture
Projects
None yet
5 participants