Diff
Modified: trunk/Source/WebKit/ChangeLog (285500 => 285501)
--- trunk/Source/WebKit/ChangeLog 2021-11-09 16:11:11 UTC (rev 285500)
+++ trunk/Source/WebKit/ChangeLog 2021-11-09 16:40:50 UTC (rev 285501)
@@ -1,3 +1,30 @@
+2021-11-09 Chris Dumez <cdu...@apple.com>
+
+ New spec: Block external protocol handler in sandboxed frames
+ https://bugs.webkit.org/show_bug.cgi?id=231727
+ <rdar://problem/84498192>
+
+ Reviewed by Brent Fulgham.
+
+ Per the HTML specification [1][2], we should prevent sandboxed iframes from opening
+ external applications by navigating to a URL with a custom procotol (e.g. rdar://).
+
+ Indeed, it would be surprising if malvertisers would be able to redirect you to
+ an external app.
+
+ To support valid use cases, we still allow such navigations in sandboxed iframes
+ if any of the following is true:
+ - sandboxFlags contains "allow-top-navigation-by-user-activation" and hasTransientActivation is true
+ - sandboxFlags contains "allow-top-navigation"
+ - sandboxFlags contains "allow-popups"
+
+ [1] https://github.com/whatwg/html/issues/2191
+ [2] https://html.spec.whatwg.org/#hand-off-to-external-software
+
+ * UIProcess/WebPageProxy.cpp:
+ (WebKit::frameSandboxAllowsOpeningExternalCustomProtocols):
+ (WebKit::WebPageProxy::decidePolicyForNavigationAction):
+
2021-11-09 Wenson Hsieh <wenson_hs...@apple.com>
[iOS] Add a position information bit to indicate whether the hit-tested element is a paused video
Modified: trunk/Source/WebKit/UIProcess/ProvisionalPageProxy.cpp (285500 => 285501)
--- trunk/Source/WebKit/UIProcess/ProvisionalPageProxy.cpp 2021-11-09 16:11:11 UTC (rev 285500)
+++ trunk/Source/WebKit/UIProcess/ProvisionalPageProxy.cpp 2021-11-09 16:40:50 UTC (rev 285501)
@@ -389,7 +389,7 @@
}
ASSERT(m_mainFrame);
- m_page.decidePolicyForNavigationActionSyncShared(m_process.copyRef(), frameID, isMainFrame, WTFMove(frameInfoData), identifier, navigationID, WTFMove(navigationActionData), WTFMove(originatingFrameInfo), originatingPageID, originalRequest, WTFMove(request), WTFMove(requestBody), WTFMove(redirectResponse), userData, WTFMove(reply));
+ m_page.decidePolicyForNavigationActionSyncShared(m_process.copyRef(), m_webPageID, frameID, isMainFrame, WTFMove(frameInfoData), identifier, navigationID, WTFMove(navigationActionData), WTFMove(originatingFrameInfo), originatingPageID, originalRequest, WTFMove(request), WTFMove(requestBody), WTFMove(redirectResponse), userData, WTFMove(reply));
}
void ProvisionalPageProxy::logDiagnosticMessageFromWebProcess(const String& message, const String& description, WebCore::ShouldSample shouldSample)
Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.cpp (285500 => 285501)
--- trunk/Source/WebKit/UIProcess/WebPageProxy.cpp 2021-11-09 16:11:11 UTC (rev 285500)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.cpp 2021-11-09 16:40:50 UTC (rev 285501)
@@ -5249,11 +5249,20 @@
process->send(Messages::WebPage::DidReceivePolicyDecision(frameID, listenerID, policyDecision, createNetworkExtensionsSandboxExtensions(process)), webPageID);
});
- decidePolicyForNavigationAction(process.copyRef(), *frame, WTFMove(frameInfo), navigationID, WTFMove(navigationActionData), WTFMove(originatingFrameInfo), originatingPageID,
+ decidePolicyForNavigationAction(process.copyRef(), webPageID, *frame, WTFMove(frameInfo), navigationID, WTFMove(navigationActionData), WTFMove(originatingFrameInfo), originatingPageID,
originalRequest, WTFMove(request), WTFMove(requestBody), WTFMove(redirectResponse), userData, WTFMove(sender));
}
-void WebPageProxy::decidePolicyForNavigationAction(Ref<WebProcessProxy>&& process, WebFrameProxy& frame, FrameInfoData&& frameInfo, uint64_t navigationID,
+// https://html.spec.whatwg.org/#hand-off-to-external-software
+static bool frameSandboxAllowsOpeningExternalCustomProtocols(SandboxFlags sandboxFlags, bool hasUserGesture)
+{
+ if (!(sandboxFlags & SandboxPopups) || !(sandboxFlags & SandboxTopNavigation))
+ return true;
+
+ return !(sandboxFlags & SandboxTopNavigationByUserActivation) && hasUserGesture;
+}
+
+void WebPageProxy::decidePolicyForNavigationAction(Ref<WebProcessProxy>&& process, PageIdentifier webPageID, WebFrameProxy& frame, FrameInfoData&& frameInfo, uint64_t navigationID,
NavigationActionData&& navigationActionData, FrameInfoData&& originatingFrameInfoData, std::optional<WebPageProxyIdentifier> originatingPageID, const WebCore::ResourceRequest& originalRequest, WebCore::ResourceRequest&& request,
IPC::FormDataReference&& requestBody, WebCore::ResourceResponse&& redirectResponse, const UserData& userData, Ref<PolicyDecisionSender>&& sender)
{
@@ -5344,6 +5353,17 @@
}
#endif
+ // Other ports do not implement WebPage::platformCanHandleRequest().
+#if PLATFORM(COCOA)
+ // Sandboxed iframes should be allowed to open external apps via custom protocols unless explicitely allowed (https://html.spec.whatwg.org/#hand-off-to-external-software).
+ bool canHandleRequest = navigationActionData.canHandleRequest || m_urlSchemeHandlersByScheme.contains(request.url().protocol().toStringWithoutCopying());
+ if (!canHandleRequest && !destinationFrameInfo->isMainFrame() && !frameSandboxAllowsOpeningExternalCustomProtocols(navigationActionData.effectiveSandboxFlags, !!navigationActionData.userGestureTokenIdentifier)) {
+ WEBPAGEPROXY_RELEASE_LOG_ERROR(Process, "Ignoring request to load this main resource because it has a custom protocol and comes from a sandboxed iframe");
+ process->send(Messages::WebPage::AddConsoleMessage(frame.frameID(), MessageSource::Security, MessageLevel::Error, "Ignoring request to load this main resource because it has a custom protocol and comes from a sandboxed iframe"_s, std::nullopt), webPageID);
+ return receivedPolicyDecision(PolicyAction::Ignore, m_navigationState->navigation(navigationID), nullptr, WTFMove(navigationAction), WTFMove(sender));
+ }
+#endif
+
ShouldExpectSafeBrowsingResult shouldExpectSafeBrowsingResult = ShouldExpectSafeBrowsingResult::Yes;
if (!m_preferences->safeBrowsingEnabled())
shouldExpectSafeBrowsingResult = ShouldExpectSafeBrowsingResult::No;
@@ -5515,10 +5535,10 @@
didCreateSubframe(frameID);
}
- decidePolicyForNavigationActionSyncShared(m_process.copyRef(), frameID, isMainFrame, WTFMove(frameInfo), identifier, navigationID, WTFMove(navigationActionData), WTFMove(originatingFrameInfo), originatingPageID, originalRequest, WTFMove(request), WTFMove(requestBody), WTFMove(redirectResponse), userData, WTFMove(reply));
+ decidePolicyForNavigationActionSyncShared(m_process.copyRef(), m_webPageID, frameID, isMainFrame, WTFMove(frameInfo), identifier, navigationID, WTFMove(navigationActionData), WTFMove(originatingFrameInfo), originatingPageID, originalRequest, WTFMove(request), WTFMove(requestBody), WTFMove(redirectResponse), userData, WTFMove(reply));
}
-void WebPageProxy::decidePolicyForNavigationActionSyncShared(Ref<WebProcessProxy>&& process, FrameIdentifier frameID, bool isMainFrame, FrameInfoData&& frameInfo, PolicyCheckIdentifier identifier, uint64_t navigationID, NavigationActionData&& navigationActionData, FrameInfoData&& originatingFrameInfo, std::optional<WebPageProxyIdentifier> originatingPageID, const WebCore::ResourceRequest& originalRequest, WebCore::ResourceRequest&& request, IPC::FormDataReference&& requestBody, WebCore::ResourceResponse&& redirectResponse, const UserData& userData, Messages::WebPageProxy::DecidePolicyForNavigationActionSync::DelayedReply&& reply)
+void WebPageProxy::decidePolicyForNavigationActionSyncShared(Ref<WebProcessProxy>&& process, PageIdentifier webPageID, FrameIdentifier frameID, bool isMainFrame, FrameInfoData&& frameInfo, PolicyCheckIdentifier identifier, uint64_t navigationID, NavigationActionData&& navigationActionData, FrameInfoData&& originatingFrameInfo, std::optional<WebPageProxyIdentifier> originatingPageID, const WebCore::ResourceRequest& originalRequest, WebCore::ResourceRequest&& request, IPC::FormDataReference&& requestBody, WebCore::ResourceResponse&& redirectResponse, const UserData& userData, Messages::WebPageProxy::DecidePolicyForNavigationActionSync::DelayedReply&& reply)
{
auto sender = PolicyDecisionSender::create(identifier, WTFMove(reply));
@@ -5525,7 +5545,7 @@
auto* frame = process->webFrame(frameID);
MESSAGE_CHECK(process, frame);
- decidePolicyForNavigationAction(WTFMove(process), *frame, WTFMove(frameInfo), navigationID, WTFMove(navigationActionData), WTFMove(originatingFrameInfo), originatingPageID, originalRequest, WTFMove(request), WTFMove(requestBody), WTFMove(redirectResponse), userData, sender.copyRef());
+ decidePolicyForNavigationAction(WTFMove(process), webPageID, *frame, WTFMove(frameInfo), navigationID, WTFMove(navigationActionData), WTFMove(originatingFrameInfo), originatingPageID, originalRequest, WTFMove(request), WTFMove(requestBody), WTFMove(redirectResponse), userData, sender.copyRef());
// If the client did not respond synchronously, proceed with the load.
sender->send(PolicyDecision { sender->identifier(), isNavigatingToAppBoundDomain(), PolicyAction::Use, navigationID, std::nullopt, std::nullopt });
Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.h (285500 => 285501)
--- trunk/Source/WebKit/UIProcess/WebPageProxy.h 2021-11-09 16:11:11 UTC (rev 285500)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.h 2021-11-09 16:40:50 UTC (rev 285501)
@@ -1766,7 +1766,7 @@
void loadDataWithNavigationShared(Ref<WebProcessProxy>&&, WebCore::PageIdentifier, API::Navigation&, const IPC::DataReference&, const String& MIMEType, const String& encoding, const String& baseURL, API::Object* userData, WebCore::ShouldTreatAsContinuingLoad, std::optional<NavigatingToAppBoundDomain>, std::optional<WebsitePoliciesData>&&, WebCore::ShouldOpenExternalURLsPolicy, WebCore::SubstituteData::SessionHistoryVisibility);
void loadRequestWithNavigationShared(Ref<WebProcessProxy>&&, WebCore::PageIdentifier, API::Navigation&, WebCore::ResourceRequest&&, WebCore::ShouldOpenExternalURLsPolicy, API::Object* userData, WebCore::ShouldTreatAsContinuingLoad, std::optional<NavigatingToAppBoundDomain>, std::optional<WebsitePoliciesData>&& = std::nullopt, std::optional<NetworkResourceLoadIdentifier> existingNetworkResourceLoadIdentifierToResume = std::nullopt);
void backForwardGoToItemShared(Ref<WebProcessProxy>&&, const WebCore::BackForwardItemIdentifier&, CompletionHandler<void(const WebBackForwardListCounts&)>&&);
- void decidePolicyForNavigationActionSyncShared(Ref<WebProcessProxy>&&, WebCore::FrameIdentifier, bool isMainFrame, FrameInfoData&&, WebCore::PolicyCheckIdentifier, uint64_t navigationID, NavigationActionData&&, FrameInfoData&& originatingFrameInfo, std::optional<WebPageProxyIdentifier> originatingPageID, const WebCore::ResourceRequest& originalRequest, WebCore::ResourceRequest&&, IPC::FormDataReference&& requestBody, WebCore::ResourceResponse&& redirectResponse, const UserData&, Messages::WebPageProxy::DecidePolicyForNavigationActionSyncDelayedReply&&);
+ void decidePolicyForNavigationActionSyncShared(Ref<WebProcessProxy>&&, WebCore::PageIdentifier, WebCore::FrameIdentifier, bool isMainFrame, FrameInfoData&&, WebCore::PolicyCheckIdentifier, uint64_t navigationID, NavigationActionData&&, FrameInfoData&& originatingFrameInfo, std::optional<WebPageProxyIdentifier> originatingPageID, const WebCore::ResourceRequest& originalRequest, WebCore::ResourceRequest&&, IPC::FormDataReference&& requestBody, WebCore::ResourceResponse&& redirectResponse, const UserData&, Messages::WebPageProxy::DecidePolicyForNavigationActionSyncDelayedReply&&);
#if USE(QUICK_LOOK)
void requestPasswordForQuickLookDocumentInMainFrameShared(const String& fileName, CompletionHandler<void(const String&)>&&);
#endif
@@ -2086,7 +2086,7 @@
void didDestroyNavigation(uint64_t navigationID);
- void decidePolicyForNavigationAction(Ref<WebProcessProxy>&&, WebFrameProxy&, FrameInfoData&&, uint64_t navigationID, NavigationActionData&&, FrameInfoData&& originatingFrameInfo,
+ void decidePolicyForNavigationAction(Ref<WebProcessProxy>&&, WebCore::PageIdentifier, WebFrameProxy&, FrameInfoData&&, uint64_t navigationID, NavigationActionData&&, FrameInfoData&& originatingFrameInfo,
std::optional<WebPageProxyIdentifier> originatingPageID, const WebCore::ResourceRequest& originalRequest, WebCore::ResourceRequest&&, IPC::FormDataReference&& requestBody,
WebCore::ResourceResponse&& redirectResponse, const UserData&, Ref<PolicyDecisionSender>&&);
void decidePolicyForNavigationActionAsync(WebCore::FrameIdentifier, FrameInfoData&&, WebCore::PolicyCheckIdentifier, uint64_t navigationID, NavigationActionData&&, FrameInfoData&& originatingFrameInfo,
Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp (285500 => 285501)
--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp 2021-11-09 16:11:11 UTC (rev 285500)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp 2021-11-09 16:40:50 UTC (rev 285501)
@@ -4306,10 +4306,10 @@
}
#endif
-void WebPage::addConsoleMessage(FrameIdentifier frameID, MessageSource messageSource, MessageLevel messageLevel, const String& message, WebCore::ResourceLoaderIdentifier requestID)
+void WebPage::addConsoleMessage(FrameIdentifier frameID, MessageSource messageSource, MessageLevel messageLevel, const String& message, std::optional<WebCore::ResourceLoaderIdentifier> requestID)
{
if (auto* frame = WebProcess::singleton().webFrame(frameID))
- frame->addConsoleMessage(messageSource, messageLevel, message, requestID.toUInt64());
+ frame->addConsoleMessage(messageSource, messageLevel, message, requestID ? requestID->toUInt64() : 0);
}
void WebPage::sendCSPViolationReport(FrameIdentifier frameID, const URL& reportURL, IPC::FormDataReference&& reportData)
Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.h (285500 => 285501)
--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.h 2021-11-09 16:11:11 UTC (rev 285500)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.h 2021-11-09 16:40:50 UTC (rev 285501)
@@ -442,7 +442,7 @@
WebFullScreenManager* fullScreenManager();
#endif
- void addConsoleMessage(WebCore::FrameIdentifier, MessageSource, MessageLevel, const String&, WebCore::ResourceLoaderIdentifier = { });
+ void addConsoleMessage(WebCore::FrameIdentifier, MessageSource, MessageLevel, const String&, std::optional<WebCore::ResourceLoaderIdentifier> = std::nullopt);
void sendCSPViolationReport(WebCore::FrameIdentifier, const URL& reportURL, IPC::FormDataReference&&);
void sendCOEPPolicyInheritenceViolation(WebCore::FrameIdentifier, const WebCore::SecurityOriginData& embedderOrigin, const String& endpoint, WebCore::COEPDisposition, const String& type, const URL& blockedURL);
void sendCOEPCORPViolation(WebCore::FrameIdentifier, const WebCore::SecurityOriginData& embedderOrigin, const String& endpoint, WebCore::COEPDisposition, WebCore::FetchOptions::Destination, const URL& blockedURL);
Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in (285500 => 285501)
--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in 2021-11-09 16:11:11 UTC (rev 285500)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in 2021-11-09 16:40:50 UTC (rev 285501)
@@ -27,7 +27,7 @@
SetBackgroundColor(std::optional<WebCore::Color> color)
- AddConsoleMessage(WebCore::FrameIdentifier frameID, enum:uint8_t JSC::MessageSource messageSource, enum:uint8_t JSC::MessageLevel messageLevel, String message, WebCore::ResourceLoaderIdentifier requestID)
+ AddConsoleMessage(WebCore::FrameIdentifier frameID, enum:uint8_t JSC::MessageSource messageSource, enum:uint8_t JSC::MessageLevel messageLevel, String message, std::optional<WebCore::ResourceLoaderIdentifier> requestID)
SendCSPViolationReport(WebCore::FrameIdentifier frameID, URL reportURL, IPC::FormDataReference reportData)
SendCOEPPolicyInheritenceViolation(WebCore::FrameIdentifier frameID, struct WebCore::SecurityOriginData embedderOrigin, String endpoint, enum:bool WebCore::COEPDisposition disposition, String type, URL blockedURL)
SendCOEPCORPViolation(WebCore::FrameIdentifier frameID, struct WebCore::SecurityOriginData embedderOrigin, String endpoint, enum:bool WebCore::COEPDisposition disposition, enum:uint8_t WebCore::FetchOptions::Destination destination, URL blockedURL)
Modified: trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm (285500 => 285501)
--- trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm 2021-11-09 16:11:11 UTC (rev 285500)
+++ trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm 2021-11-09 16:40:50 UTC (rev 285501)
@@ -629,10 +629,9 @@
return 0;
}
-bool WebPage::platformCanHandleRequest(const WebCore::ResourceRequest&)
+bool WebPage::platformCanHandleRequest(const WebCore::ResourceRequest& request)
{
- notImplemented();
- return false;
+ return [NSURLConnection canHandleRequest:request.nsURLRequest(HTTPBodyUpdatePolicy::DoNotUpdateHTTPBody)];
}
void WebPage::shouldDelayWindowOrderingEvent(const WebKit::WebMouseEvent&, CompletionHandler<void(bool)>&& completionHandler)
Modified: trunk/Tools/ChangeLog (285500 => 285501)
--- trunk/Tools/ChangeLog 2021-11-09 16:11:11 UTC (rev 285500)
+++ trunk/Tools/ChangeLog 2021-11-09 16:40:50 UTC (rev 285501)
@@ -1,3 +1,16 @@
+2021-11-09 Chris Dumez <cdu...@apple.com>
+
+ New spec: Block external protocol handler in sandboxed frames
+ https://bugs.webkit.org/show_bug.cgi?id=231727
+ <rdar://problem/84498192>
+
+ Reviewed by Brent Fulgham.
+
+ Add API test coverage.
+
+ * TestWebKitAPI/Tests/WebKitCocoa/Navigation.mm:
+ (TEST):
+
2021-11-09 Carlos Alberto Lopez Perez <clo...@igalia.com>
[EWS] Allow the optimization of running only the subset of failed tests on run-layout-tests-without-patch also for patches modifying the TestExpectations files
Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/Navigation.mm (285500 => 285501)
--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/Navigation.mm 2021-11-09 16:11:11 UTC (rev 285500)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/Navigation.mm 2021-11-09 16:40:50 UTC (rev 285501)
@@ -823,3 +823,190 @@
[webView loadRequest:server.request()];
Util::run(&finishedNavigation);
}
+
+TEST(WKNavigation, LoadRadarURLFromSandboxedFrameAllowPopups)
+{
+ const char* mainHTML = "<iframe src='' sandbox='allow-scripts allow-popups'></iframe>";
+ const char* frameHTML = "<a id='testLink' href=''>Link</a><script>setTimeout(() => { document.getElementById('testLink').click() }, 0);</script>";
+
+ using namespace TestWebKitAPI;
+ HTTPServer server({
+ { "/", { mainHTML } },
+ { "/frame.html", { frameHTML } },
+ });
+
+ auto webView = adoptNS([WKWebView new]);
+ auto delegate = adoptNS([TestNavigationDelegate new]);
+ [webView setNavigationDelegate:delegate.get()];
+
+ __block bool didTryToLoadRadarURL = false;
+ delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
+ if ([action.request.URL.scheme isEqualToString:@"rdar"]) {
+ didTryToLoadRadarURL = true;
+ completionHandler(WKNavigationActionPolicyCancel);
+ } else
+ completionHandler(WKNavigationActionPolicyAllow);
+ };
+
+ __block bool finishedNavigation = false;
+ delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
+ finishedNavigation = true;
+ };
+
+ [webView loadRequest:server.request()];
+ Util::run(&finishedNavigation);
+
+ Util::run(&didTryToLoadRadarURL);
+}
+
+TEST(WKNavigation, LoadRadarURLFromSandboxedFrameAllowTopNavigation)
+{
+ const char* mainHTML = "<iframe src='' sandbox='allow-scripts allow-top-navigation'></iframe>";
+ const char* frameHTML = "<a id='testLink' href=''>Link</a><script>setTimeout(() => { document.getElementById('testLink').click() }, 0);</script>";
+
+ using namespace TestWebKitAPI;
+ HTTPServer server({
+ { "/", { mainHTML } },
+ { "/frame.html", { frameHTML } },
+ });
+
+ auto webView = adoptNS([WKWebView new]);
+ auto delegate = adoptNS([TestNavigationDelegate new]);
+ [webView setNavigationDelegate:delegate.get()];
+
+ __block bool didTryToLoadRadarURL = false;
+ delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
+ if ([action.request.URL.scheme isEqualToString:@"rdar"]) {
+ didTryToLoadRadarURL = true;
+ completionHandler(WKNavigationActionPolicyCancel);
+ } else
+ completionHandler(WKNavigationActionPolicyAllow);
+ };
+
+ __block bool finishedNavigation = false;
+ delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
+ finishedNavigation = true;
+ };
+
+ [webView loadRequest:server.request()];
+ Util::run(&finishedNavigation);
+
+ Util::run(&didTryToLoadRadarURL);
+}
+
+TEST(WKNavigation, LoadRadarURLFromSandboxedFrameWithUserGesture)
+{
+ const char* mainHTML = "<iframe src='' sandbox='allow-scripts allow-top-navigation-by-user-activation'></iframe>";
+ const char* frameHTML = "<a id='testLink' href=''>Link</a></script>";
+
+ using namespace TestWebKitAPI;
+ HTTPServer server({
+ { "/", { mainHTML } },
+ { "/frame.html", { frameHTML } },
+ });
+
+ auto webView = adoptNS([WKWebView new]);
+ auto delegate = adoptNS([TestNavigationDelegate new]);
+ [webView setNavigationDelegate:delegate.get()];
+
+ __block RetainPtr<WKFrameInfo> iframe;
+ __block bool didTryToLoadRadarURL = false;
+ delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
+ if (!action.targetFrame.isMainFrame)
+ iframe = action.targetFrame;
+ if ([action.request.URL.scheme isEqualToString:@"rdar"]) {
+ didTryToLoadRadarURL = true;
+ completionHandler(WKNavigationActionPolicyCancel);
+ } else
+ completionHandler(WKNavigationActionPolicyAllow);
+ };
+
+ __block bool finishedNavigation = false;
+ delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
+ finishedNavigation = true;
+ };
+
+ [webView loadRequest:server.request()];
+ Util::run(&finishedNavigation);
+
+ ASSERT_TRUE(!!iframe);
+
+ // Running _javascript_ simulates a user gesture so the navigation should be permitted due to 'allow-top-navigation-by-user-activation'.
+ [webView evaluateJavaScript:@"document.getElementById('testLink').click()" inFrame:iframe.get() inContentWorld:WKContentWorld.pageWorld completionHandler:nil];
+
+ Util::run(&didTryToLoadRadarURL);
+}
+
+TEST(WKNavigation, LoadRadarURLFromSandboxedFrame)
+{
+ const char* mainHTML = "<iframe src='' sandbox='allow-scripts'></iframe>";
+ const char* frameHTML = "<a id='testLink' href=''>Link</a><script>setTimeout(() => { document.getElementById('testLink').click() }, 0);</script>";
+
+ using namespace TestWebKitAPI;
+ HTTPServer server({
+ { "/", { mainHTML } },
+ { "/frame.html", { frameHTML } },
+ });
+
+ auto webView = adoptNS([WKWebView new]);
+ auto delegate = adoptNS([TestNavigationDelegate new]);
+ [webView setNavigationDelegate:delegate.get()];
+
+ __block bool didTryToLoadRadarURL = false;
+ delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
+ if ([action.request.URL.scheme isEqualToString:@"rdar"]) {
+ didTryToLoadRadarURL = true;
+ completionHandler(WKNavigationActionPolicyCancel);
+ } else
+ completionHandler(WKNavigationActionPolicyAllow);
+ };
+
+ __block bool finishedNavigation = false;
+ delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
+ finishedNavigation = true;
+ };
+
+ [webView loadRequest:server.request()];
+ Util::run(&finishedNavigation);
+
+ Util::sleep(0.5);
+
+ EXPECT_FALSE(didTryToLoadRadarURL);
+}
+
+TEST(WKNavigation, LoadRadarURLFromSandboxedFrameMissingUserGesture)
+{
+ const char* mainHTML = "<iframe src='' sandbox='allow-scripts allow-top-navigation-by-user-activation'></iframe>";
+ const char* frameHTML = "<a id='testLink' href=''>Link</a><script>setTimeout(() => { document.getElementById('testLink').click() }, 0);</script>";
+
+ using namespace TestWebKitAPI;
+ HTTPServer server({
+ { "/", { mainHTML } },
+ { "/frame.html", { frameHTML } },
+ });
+
+ auto webView = adoptNS([WKWebView new]);
+ auto delegate = adoptNS([TestNavigationDelegate new]);
+ [webView setNavigationDelegate:delegate.get()];
+
+ __block bool didTryToLoadRadarURL = false;
+ delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
+ if ([action.request.URL.scheme isEqualToString:@"rdar"]) {
+ didTryToLoadRadarURL = true;
+ completionHandler(WKNavigationActionPolicyCancel);
+ } else
+ completionHandler(WKNavigationActionPolicyAllow);
+ };
+
+ __block bool finishedNavigation = false;
+ delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
+ finishedNavigation = true;
+ };
+
+ [webView loadRequest:server.request()];
+ Util::run(&finishedNavigation);
+
+ Util::sleep(0.5);
+
+ EXPECT_FALSE(didTryToLoadRadarURL);
+}