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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions Source/WebKit/Platform/CoroutineUtilities.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
* 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
// FIXME: Remove this once all supported toolchains have non-experimental coroutine support.
#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 PromiseBase {
public:
struct final_awaitable {
bool await_ready() const noexcept { return false; }
template<typename Promise>
std::coroutine_handle<> await_suspend(std::coroutine_handle<Promise> coroutine) noexcept { return coroutine.promise().handle(); }
void await_resume() noexcept { }
};
std::suspend_always initial_suspend() { return { }; }
final_awaitable final_suspend() noexcept { return { }; }
template<typename Promise>
void unhandled_exception() { }
void setHandle(std::coroutine_handle<> handle) { m_handle = handle; }
std::coroutine_handle<> handle() { return m_handle; }
private:
std::coroutine_handle<> m_handle;
};
template<typename U>
class Promise final : public PromiseBase {
public:
Lazy<U> get_return_object() { return Lazy<U> { std::coroutine_handle<Promise>::from_promise(*this) }; }
void return_value(U&& value) { m_value = WTFMove(value); }
U result() && { return std::exchange(m_value, { }); }
private:
U m_value;
};
template<std::same_as<void> U>
class Promise<U> : public PromiseBase {
public:
Lazy<void> get_return_object() { return Lazy<void> { std::coroutine_handle<Promise>::from_promise(*this) }; }
void return_void() { }
void result() { }
};
using promise_type = Promise<T>;

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;
};

static void callCoroutine(auto coroutine)
{
CoroutineCaller* caller = new CoroutineCaller();
caller->setCoroutine([caller, coroutine = WTFMove(coroutine)] () mutable -> Task {
co_await coroutine();
delete caller;
});
}

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;
};

}
10 changes: 10 additions & 0 deletions Source/WebKit/Platform/IPC/Connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ extern ASCIILiteral errorAsString(Error);
#define CONNECTION_STRINGIFY_MACRO(line) CONNECTION_STRINGIFY(line)

#define MESSAGE_CHECK_BASE(assertion, connection) MESSAGE_CHECK_COMPLETION_BASE(assertion, connection, (void)0)
#define MESSAGE_CHECK_BASE_COROUTINE(assertion, connection) MESSAGE_CHECK_COMPLETION_BASE_COROUTINE(assertion, connection, (void)0)

#define MESSAGE_CHECK_COMPLETION_BASE(assertion, connection, completion) do { \
if (UNLIKELY(!(assertion))) { \
Expand All @@ -125,6 +126,15 @@ extern ASCIILiteral errorAsString(Error);
} \
} while (0)

#define MESSAGE_CHECK_COMPLETION_BASE_COROUTINE(assertion, connection, completion) do { \
if (UNLIKELY(!(assertion))) { \
RELEASE_LOG_FAULT(IPC, __FILE__ " " CONNECTION_STRINGIFY_MACRO(__LINE__) ": Invalid message dispatched %s", WTF_PRETTY_FUNCTION); \
(connection)->markCurrentlyDispatchedMessageAsInvalid(); \
{ completion; } \
co_return { }; \
} \
} while (0)

#define MESSAGE_CHECK_WITH_RETURN_VALUE_BASE(assertion, connection, returnValue) do { \
if (UNLIKELY(!(assertion))) { \
RELEASE_LOG_FAULT(IPC, __FILE__ " " CONNECTION_STRINGIFY_MACRO(__LINE__) ": Invalid message dispatched %" PUBLIC_LOG_STRING, WTF_PRETTY_FUNCTION); \
Expand Down
39 changes: 31 additions & 8 deletions Source/WebKit/UIProcess/WebPageProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
#include "AuthenticatorManager.h"
#include "BrowsingContextGroup.h"
#include "Connection.h"
#include "CoroutineUtilities.h"
#include "DidFilterKnownLinkDecoration.h"
#include "DownloadManager.h"
#include "DownloadProxy.h"
Expand Down Expand Up @@ -402,6 +403,7 @@

#define MESSAGE_CHECK(process, assertion) MESSAGE_CHECK_BASE(assertion, process->connection())
#define MESSAGE_CHECK_URL(process, url) MESSAGE_CHECK_BASE(checkURLReceivedFromCurrentOrPreviousWebProcess(process, url), process->connection())
#define MESSAGE_CHECK_URL_COROUTINE(process, url) MESSAGE_CHECK_BASE_COROUTINE(checkURLReceivedFromCurrentOrPreviousWebProcess(process, url), process->connection())
#define MESSAGE_CHECK_COMPLETION(process, assertion, completion) MESSAGE_CHECK_COMPLETION_BASE(assertion, process->connection(), completion)

#define WEBPAGEPROXY_RELEASE_LOG(channel, fmt, ...) RELEASE_LOG(channel, "%p - [pageProxyID=%" PRIu64 ", webPageID=%" PRIu64 ", PID=%i] WebPageProxy::" fmt, this, internals().identifier.toUInt64(), internals().webPageID.toUInt64(), m_process->processID(), ##__VA_ARGS__)
Expand Down Expand Up @@ -6938,6 +6940,13 @@ static bool frameSandboxAllowsOpeningExternalCustomProtocols(SandboxFlags sandbo
#endif

void WebPageProxy::decidePolicyForNavigationAction(Ref<WebProcessProxy>&& process, WebFrameProxy& frame, NavigationActionData&& navigationActionData, CompletionHandler<void(PolicyDecision&&)>&& completionHandler)
{
callCoroutine([this, protectedThis = Ref { *this }, process = WTFMove(process), frame = Ref { frame }, navigationActionData = WTFMove(navigationActionData), completionHandler = WTFMove(completionHandler)] () mutable -> Lazy<void> {
completionHandler(co_await awaitableDecidePolicyForNavigationAction(WTFMove(process), frame, WTFMove(navigationActionData)));
});
}

Lazy<PolicyDecision> WebPageProxy::awaitableDecidePolicyForNavigationAction(Ref<WebProcessProxy>&& process, WebFrameProxy& frame, NavigationActionData&& navigationActionData)
{
auto frameInfo = navigationActionData.frameInfo;
auto navigationID = navigationActionData.navigationID;
Expand All @@ -6952,19 +6961,18 @@ void WebPageProxy::decidePolicyForNavigationAction(Ref<WebProcessProxy>&& proces

Ref protectedPageClient { pageClient() };

auto transaction = internals().pageLoadState.transaction();
auto transaction = std::optional(internals().pageLoadState.transaction());

bool fromAPI = request.url() == internals().pageLoadState.pendingAPIRequestURL();
if (navigationID && !fromAPI)
internals().pageLoadState.clearPendingAPIRequest(transaction);
internals().pageLoadState.clearPendingAPIRequest(*transaction);

if (!checkURLReceivedFromCurrentOrPreviousWebProcess(process, request.url())) {
WEBPAGEPROXY_RELEASE_LOG_ERROR(Process, "Ignoring request to load this main resource because it is outside the sandbox");
completionHandler(PolicyDecision { isNavigatingToAppBoundDomain() });
return;
co_return PolicyDecision { isNavigatingToAppBoundDomain() };
}

MESSAGE_CHECK_URL(process, originalRequest.url());
MESSAGE_CHECK_URL_COROUTINE(process, originalRequest.url());

RefPtr<API::Navigation> navigation;
if (navigationID)
Expand Down Expand Up @@ -7029,7 +7037,10 @@ void WebPageProxy::decidePolicyForNavigationAction(Ref<WebProcessProxy>&& proces
#if ENABLE(CONTENT_FILTERING)
if (frame.didHandleContentFilterUnblockNavigation(request)) {
WEBPAGEPROXY_RELEASE_LOG_ERROR(Process, "Ignoring request to load this main resource because it was handled by content filter");
return receivedPolicyDecision(PolicyAction::Ignore, RefPtr { m_navigationState->navigation(navigationID) }.get(), nullptr, WTFMove(navigationAction), WillContinueLoadInNewProcess::No, std::nullopt, std::nullopt, WTFMove(completionHandler));
CompletionHandler<void(CompletionHandler<void(PolicyDecision&&)>&&)> completionHandler { [this, protectedThis = Ref { *this }, navigationAction = WTFMove(navigationAction), navigationID] (CompletionHandler<void(PolicyDecision&&)>&& completionHandler) mutable {
receivedPolicyDecision(PolicyAction::Ignore, RefPtr { m_navigationState->navigation(navigationID) }.get(), nullptr, WTFMove(navigationAction), WillContinueLoadInNewProcess::No, std::nullopt, { }, WTFMove(completionHandler));
} };
co_return co_await AwaitableTaskWithCompletionHandler<PolicyDecision>(WTFMove(completionHandler));
}
#endif

Expand All @@ -7047,7 +7058,10 @@ void WebPageProxy::decidePolicyForNavigationAction(Ref<WebProcessProxy>&& proces
MessageSource::Security,
"Ignoring request to load this main resource because it has a custom protocol and comes from a sandboxed iframe"_s
};
return receivedPolicyDecision(PolicyAction::Ignore, RefPtr { m_navigationState->navigation(navigationID) }.get(), nullptr, WTFMove(navigationAction), WillContinueLoadInNewProcess::No, std::nullopt, WTFMove(errorMessage), WTFMove(completionHandler));
CompletionHandler<void(CompletionHandler<void(PolicyDecision&&)>&&)> completionHandler { [this, protectedThis = Ref { *this }, navigationAction = WTFMove(navigationAction), navigationID] (CompletionHandler<void(PolicyDecision&&)>&& completionHandler) mutable {
receivedPolicyDecision(PolicyAction::Ignore, RefPtr { m_navigationState->navigation(navigationID) }.get(), nullptr, WTFMove(navigationAction), WillContinueLoadInNewProcess::No, std::nullopt, { }, WTFMove(completionHandler));
} };
co_return co_await AwaitableTaskWithCompletionHandler<PolicyDecision>(WTFMove(completionHandler));
}
message = PolicyDecisionConsoleMessage {
MessageLevel::Warning,
Expand All @@ -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.

CompletionHandler<void(CompletionHandler<void(PolicyDecision&&)>&&)> completionHandler { [this, protectedThis = Ref { *this }, navigationAction = WTFMove(navigationAction), frame = Ref { frame }, process = WTFMove(process), navigation = WTFMove(navigation), frameInfo = WTFMove(frameInfo), shouldExpectSafeBrowsingResult, shouldExpectAppBoundDomainResult, shouldWaitForInitialLinkDecorationFilteringData, request = WTFMove(request), originalRequest = WTFMove(originalRequest), originatingFrame = WTFMove(originatingFrame), message = WTFMove(message)] (CompletionHandler<void(PolicyDecision&&)>&& completionHandler) mutable {
continueDecidePolicyForNavigationAction(WTFMove(navigationAction), frame, WTFMove(process), WTFMove(navigation), WTFMove(frameInfo), shouldExpectSafeBrowsingResult, shouldExpectAppBoundDomainResult, shouldWaitForInitialLinkDecorationFilteringData, WTFMove(request), WTFMove(originalRequest), WTFMove(originatingFrame), WTFMove(message), WTFMove(completionHandler));
} };
co_return co_await AwaitableTaskWithCompletionHandler<PolicyDecision>(WTFMove(completionHandler));
}

void WebPageProxy::continueDecidePolicyForNavigationAction(Ref<API::NavigationAction>&& navigationAction, WebFrameProxy& frame, Ref<WebProcessProxy>&& process, RefPtr<API::Navigation>&& navigation, FrameInfoData&& frameInfo, ShouldExpectSafeBrowsingResult shouldExpectSafeBrowsingResult, ShouldExpectAppBoundDomainResult shouldExpectAppBoundDomainResult, ShouldWaitForInitialLinkDecorationFilteringData shouldWaitForInitialLinkDecorationFilteringData, WebCore::ResourceRequest&& request, WebCore::ResourceRequest&& originalRequest, RefPtr<WebFrameProxy>&& originatingFrame, std::optional<PolicyDecisionConsoleMessage>&& message, CompletionHandler<void(PolicyDecision&&)>&& completionHandler)
{
Ref listener = frame.setUpPolicyListenerProxy([
this,
protectedThis = Ref { *this },
Expand All @@ -7085,7 +7108,7 @@ void WebPageProxy::decidePolicyForNavigationAction(Ref<WebProcessProxy>&& proces
message = WTFMove(message),
frameInfo,
requestURL = request.url(),
protectedPageClient
protectedPageClient = Ref { pageClient() }
] (PolicyAction policyAction, API::WebsitePolicies* policies, ProcessSwapRequestedByClient processSwapRequestedByClient, RefPtr<SafeBrowsingWarning>&& safeBrowsingWarning, std::optional<NavigatingToAppBoundDomain> isAppBoundDomain, WasNavigationIntercepted wasNavigationIntercepted) mutable {
WEBPAGEPROXY_RELEASE_LOG(Loading, "decidePolicyForNavigationAction: listener called: frameID=%" PRIu64 ", isMainFrame=%d, navigationID=%" PRIu64 ", policyAction=%u, safeBrowsingWarning=%d, isAppBoundDomain=%d, wasNavigationIntercepted=%d", frame->frameID().object().toUInt64(), frame->isMainFrame(), navigation ? navigation->navigationID() : 0, (unsigned)policyAction, !!safeBrowsingWarning, !!isAppBoundDomain, wasNavigationIntercepted == WasNavigationIntercepted::Yes);

Expand Down
6 changes: 6 additions & 0 deletions Source/WebKit/UIProcess/WebPageProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,9 @@ enum class SameDocumentNavigationType : uint8_t;
enum class SelectionFlags : uint8_t;
enum class SelectionTouch : uint8_t;
enum class ShouldDelayClosingUntilFirstLayerFlush : bool;
enum class ShouldExpectAppBoundDomainResult : bool;
enum class ShouldExpectSafeBrowsingResult : bool;
enum class ShouldWaitForInitialLinkDecorationFilteringData : bool;
enum class SyntheticEditingCommandType : uint8_t;
enum class TextRecognitionUpdateResult : uint8_t;
enum class TextIndicatorStyle : uint8_t;
Expand All @@ -540,6 +543,7 @@ enum class WebTextReplacementDataState : uint8_t;
enum class WebUnifiedTextReplacementSessionDataReplacementType : uint8_t;
enum class WindowKind : uint8_t;

template<typename> class Lazy;
template<typename> class MonotonicObjectIdentifier;

using ActivityStateChangeID = uint64_t;
Expand Down Expand Up @@ -2548,6 +2552,8 @@ class WebPageProxy final : public API::ObjectImpl<API::Object::Type::Page>, publ
void didDestroyNavigation(uint64_t navigationID);

void decidePolicyForNavigationAction(Ref<WebProcessProxy>&&, WebFrameProxy&, NavigationActionData&&, CompletionHandler<void(PolicyDecision&&)>&&);
Lazy<PolicyDecision> awaitableDecidePolicyForNavigationAction(Ref<WebProcessProxy>&&, WebFrameProxy&, NavigationActionData&&);
void continueDecidePolicyForNavigationAction(Ref<API::NavigationAction>&&, WebFrameProxy&, Ref<WebProcessProxy>&&, RefPtr<API::Navigation>&&, FrameInfoData&&, ShouldExpectSafeBrowsingResult, ShouldExpectAppBoundDomainResult, ShouldWaitForInitialLinkDecorationFilteringData, WebCore::ResourceRequest&&, WebCore::ResourceRequest&& originalRequest, RefPtr<WebFrameProxy>&& originatingFrame, std::optional<PolicyDecisionConsoleMessage>&&, CompletionHandler<void(PolicyDecision&&)>&&);
void decidePolicyForNavigationActionAsync(NavigationActionData&&, CompletionHandler<void(PolicyDecision&&)>&&);
void decidePolicyForNavigationActionSync(IPC::Connection&, NavigationActionData&&, CompletionHandler<void(PolicyDecision&&)>&&);
void decidePolicyForNewWindowAction(NavigationActionData&&, const String& frameName, CompletionHandler<void(PolicyDecision&&)>&&);
Expand Down
Loading