blob: bbf3c584fd3724bee1d11e976442ebd0a5ff2466 [file] [log] [blame] [edit]
/*
* Copyright (C) 2022 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.
*/
#import "config.h"
#import "FrameTreeChecks.h"
#import "HTTPServer.h"
#import "TestNavigationDelegate.h"
#import "Utilities.h"
#import <WebKit/WKFrameInfoPrivate.h>
#import <WebKit/WKNavigationPrivate.h>
#import <WebKit/WKPreferencesPrivate.h>
#import <WebKit/WKWebViewPrivateForTesting.h>
#import <WebKit/_WKFeature.h>
#import <WebKit/_WKFrameTreeNode.h>
#import <wtf/BlockPtr.h>
namespace TestWebKitAPI {
static void enableSiteIsolation(WKWebViewConfiguration *configuration)
{
auto preferences = [configuration preferences];
for (_WKFeature *feature in [WKPreferences _features]) {
if ([feature.key isEqualToString:@"SiteIsolationEnabled"]) {
[preferences _setEnabled:YES forFeature:feature];
break;
}
}
}
static void enableWindowOpenPSON(WKWebViewConfiguration *configuration)
{
auto preferences = [configuration preferences];
for (_WKFeature *feature in [WKPreferences _features]) {
if ([feature.key isEqualToString:@"ProcessSwapOnCrossSiteWindowOpenEnabled"]) {
[preferences _setEnabled:YES forFeature:feature];
break;
}
}
}
static bool processStillRunning(pid_t pid)
{
return !kill(pid, 0);
}
static bool frameTreesMatch(_WKFrameTreeNode *actualRoot, const ExpectedFrameTree& expectedRoot)
{
WKFrameInfo *info = actualRoot.info;
if (info._isLocalFrame != std::holds_alternative<String>(expectedRoot.remoteOrOrigin))
return false;
if (auto* expectedOrigin = std::get_if<String>(&expectedRoot.remoteOrOrigin)) {
WKSecurityOrigin *origin = info.securityOrigin;
auto actualOrigin = makeString(String(origin.protocol), "://"_s, String(origin.host), origin.port ? makeString(':', origin.port) : String());
if (actualOrigin != *expectedOrigin)
return false;
}
if (actualRoot.childFrames.count != expectedRoot.children.size())
return false;
for (size_t i = 0; i < expectedRoot.children.size(); i++) {
if (!frameTreesMatch(actualRoot.childFrames[i], expectedRoot.children[i]))
return false;
}
return true;
}
static bool frameTreesMatch(NSSet<_WKFrameTreeNode *> *actualFrameTrees, Vector<ExpectedFrameTree>&& expectedFrameTrees)
{
if (actualFrameTrees.count != expectedFrameTrees.size())
return false;
for (_WKFrameTreeNode *root in actualFrameTrees) {
auto index = expectedFrameTrees.findIf([&] (auto& expectedFrameTree) {
return frameTreesMatch(root, expectedFrameTree);
});
if (index == WTF::notFound)
return false;
expectedFrameTrees.remove(index);
}
return expectedFrameTrees.isEmpty();
}
static RetainPtr<NSSet> frameTrees(WKWebView *webView)
{
__block RetainPtr<NSSet> result;
[webView _frameTrees:^(NSSet<_WKFrameTreeNode *> *frameTrees) {
result = frameTrees;
}];
while (!result)
Util::spinRunLoop();
return result;
}
static void checkFrameTreesInProcesses(NSSet<_WKFrameTreeNode *> *actualTrees, Vector<ExpectedFrameTree>&& expectedFrameTrees)
{
EXPECT_TRUE(frameTreesMatch(actualTrees, WTFMove(expectedFrameTrees)));
}
void checkFrameTreesInProcesses(WKWebView *webView, Vector<ExpectedFrameTree>&& expectedFrameTrees)
{
checkFrameTreesInProcesses(frameTrees(webView).get(), WTFMove(expectedFrameTrees));
}
enum class FrameType : bool { Local, Remote };
static pid_t findFramePID(NSSet<_WKFrameTreeNode *> *set, FrameType local)
{
for (_WKFrameTreeNode *node in set) {
if (node.info._isLocalFrame == (local == FrameType::Local))
return node.info._processIdentifier;
}
ASSERT_NOT_REACHED();
return 0;
}
TEST(SiteIsolation, LoadingCallbacksAndPostMessage)
{
auto exampleHTML = "<script>"
" window.addEventListener('message', (event) => {"
" alert('parent frame received ' + event.data)"
" }, false);"
" onload = () => {"
" document.getElementById('webkit_frame').contentWindow.postMessage('ping', '*');"
" }"
"</script>"
"<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe>"_s;
auto webkitHTML = "<script>"
" window.addEventListener('message', (event) => {"
" parent.window.postMessage(event.data + 'pong', { 'targetOrigin' : '*' });"
" }, false)"
"</script>"_s;
bool finishedLoading { false };
size_t framesCommitted { 0 };
HTTPServer server(HTTPServer::UseCoroutines::Yes, [&](Connection connection) -> Task {
while (1) {
auto request = co_await connection.awaitableReceiveHTTPRequest();
auto path = HTTPServer::parsePath(request);
if (path == "/example"_s) {
co_await connection.awaitableSend(HTTPResponse(exampleHTML).serialize());
continue;
}
if (path == "/webkit"_s) {
size_t contentLength = 2000000 + webkitHTML.length();
co_await connection.awaitableSend(makeString("HTTP/1.1 200 OK\r\nContent-Length: "_s, contentLength, "\r\n\r\n"_s));
co_await connection.awaitableSend(webkitHTML);
co_await connection.awaitableSend(Vector<uint8_t>(1000000, ' '));
while (framesCommitted < 2)
Util::spinRunLoop();
Util::runFor(Seconds(0.5));
EXPECT_EQ(framesCommitted, 2u);
EXPECT_FALSE(finishedLoading);
co_await connection.awaitableSend(Vector<uint8_t>(1000000, ' '));
continue;
}
EXPECT_FALSE(true);
}
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
navigationDelegate.get().didCommitLoadWithRequestInFrame = makeBlockPtr([&](WKWebView *, NSURLRequest *, WKFrameInfo *frameInfo) {
NSString *url = frameInfo.request.URL.absoluteString;
switch (++framesCommitted) {
case 1:
EXPECT_WK_STREQ(url, "https://example.com/example");
EXPECT_TRUE(frameInfo.isMainFrame);
break;
case 2:
EXPECT_WK_STREQ(url, "https://webkit.org/webkit");
EXPECT_FALSE(frameInfo.isMainFrame);
break;
default:
EXPECT_FALSE(true);
break;
}
}).get();
navigationDelegate.get().didFinishNavigation = makeBlockPtr([&](WKWebView *, WKNavigation *navigation) {
if (navigation._request) {
EXPECT_WK_STREQ(navigation._request.URL.absoluteString, "https://example.com/example");
finishedLoading = true;
}
}).get();
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
__block RetainPtr<NSString> alert;
auto uiDelegate = adoptNS([TestUIDelegate new]);
uiDelegate.get().runJavaScriptAlertPanelWithMessage = ^(WKWebView *, NSString *message, WKFrameInfo *, void (^completionHandler)(void)) {
alert = message;
completionHandler();
};
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
webView.get().UIDelegate = uiDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
Util::run(&finishedLoading);
while (!alert)
Util::spinRunLoop();
EXPECT_WK_STREQ(alert.get(), "parent frame received pingpong");
checkFrameTreesInProcesses(webView.get(), {
{ "https://example.com"_s,
{ { RemoteFrame } }
}, { RemoteFrame,
{ { "https://webkit.org"_s } }
},
});
}
TEST(SiteIsolation, BasicPostMessageWindowOpen)
{
auto exampleHTML = "<script>"
" window.addEventListener('message', (event) => {"
" w.postMessage('pong', '*');"
" }, false);"
"</script>"_s;
auto webkitHTML = "<script>"
" window.addEventListener('message', (event) => {"
" alert('opened page received ' + event.data);"
" }, false);"
"</script>"_s;
__block bool openerFinishedLoading { false };
__block bool openedFinishedLoading { false };
HTTPServer server({
{ "/example"_s, { exampleHTML } },
{ "/webkit"_s, { webkitHTML } }
}, HTTPServer::Protocol::HttpsProxy);
__block RetainPtr<WKWebView> openerWebView;
__block RetainPtr<WKWebView> openedWebView;
auto openerNavigationDelegate = adoptNS([TestNavigationDelegate new]);
[openerNavigationDelegate allowAnyTLSCertificate];
openerNavigationDelegate.get().didFinishNavigation = ^(WKWebView *opener, WKNavigation *navigation) {
EXPECT_WK_STREQ(navigation._request.URL.absoluteString, "https://example.com/example");
checkFrameTreesInProcesses(opener, { { "https://example.com"_s } });
openerFinishedLoading = true;
};
__block auto openedNavigationDelegate = adoptNS([TestNavigationDelegate new]);
[openedNavigationDelegate allowAnyTLSCertificate];
openedNavigationDelegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *navigation) {
EXPECT_WK_STREQ(navigation._request.URL.absoluteString, "https://webkit.org/webkit");
checkFrameTreesInProcesses(openerWebView.get(), { { "https://example.com"_s }, { RemoteFrame } });
checkFrameTreesInProcesses(openedWebView.get(), { { "https://webkit.org"_s }, { RemoteFrame } });
auto openerFrames = frameTrees(openerWebView.get());
auto openedFrames = frameTrees(openedWebView.get());
EXPECT_NE([openerWebView _webProcessIdentifier], [openedWebView _webProcessIdentifier]);
EXPECT_EQ(findFramePID(openerFrames.get(), FrameType::Remote), [openedWebView _webProcessIdentifier]);
EXPECT_EQ(findFramePID(openedFrames.get(), FrameType::Remote), [openerWebView _webProcessIdentifier]);
openedFinishedLoading = true;
};
openedNavigationDelegate.get().decidePolicyForNavigationResponse = ^(WKNavigationResponse *, void (^completionHandler)(WKNavigationResponsePolicy)) {
auto openerFrames = frameTrees(openerWebView.get());
checkFrameTreesInProcesses(openerFrames.get(), { { "https://example.com"_s }, { RemoteFrame } });
checkFrameTreesInProcesses(openedWebView.get(), { { "https://example.com"_s } });
EXPECT_EQ([openerWebView _webProcessIdentifier], [openedWebView _webProcessIdentifier]);
EXPECT_NE([openedWebView _webProcessIdentifier], [openedWebView _provisionalWebProcessIdentifier]);
EXPECT_EQ(findFramePID(openerFrames.get(), FrameType::Remote), [openedWebView _provisionalWebProcessIdentifier]);
EXPECT_EQ(findFramePID(openerFrames.get(), FrameType::Local), [openerWebView _webProcessIdentifier]);
completionHandler(WKNavigationResponsePolicyAllow);
};
auto configuration = server.httpsProxyConfiguration();
enableWindowOpenPSON(configuration);
__block RetainPtr<NSString> alert;
auto uiDelegate = adoptNS([TestUIDelegate new]);
uiDelegate.get().runJavaScriptAlertPanelWithMessage = ^(WKWebView *, NSString *message, WKFrameInfo *, void (^completionHandler)(void)) {
alert = message;
completionHandler();
};
uiDelegate.get().createWebViewWithConfiguration = ^(WKWebViewConfiguration *configuration, WKNavigationAction *action, WKWindowFeatures *windowFeatures) {
openedWebView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
openedWebView.get().UIDelegate = uiDelegate.get();
openedWebView.get().navigationDelegate = openedNavigationDelegate.get();
return openedWebView.get();
};
openerWebView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
openerWebView.get().navigationDelegate = openerNavigationDelegate.get();
openerWebView.get().UIDelegate = uiDelegate.get();
openerWebView.get().configuration.preferences.javaScriptCanOpenWindowsAutomatically = YES;
[openerWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
Util::run(&openerFinishedLoading);
[openerWebView evaluateJavaScript:@"w = window.open('https://webkit.org/webkit')" completionHandler:nil];
Util::run(&openedFinishedLoading);
[openedWebView evaluateJavaScript:@"try { window.opener.postMessage('ping', '*'); } catch(e) { alert('error ' + e) }" completionHandler:nil];
while (!alert)
Util::spinRunLoop();
EXPECT_WK_STREQ(alert.get(), "opened page received pong");
}
TEST(SiteIsolation, PostMessageWithMessagePorts)
{
auto exampleHTML = "<script>"
" const channel = new MessageChannel();"
" channel.port1.onmessage = function() {"
" alert('parent frame received ' + event.data)"
" };"
" onload = () => {"
" document.getElementById('webkit_frame').contentWindow.postMessage('ping', '*', [channel.port2]);"
" }"
"</script>"
"<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe>"_s;
auto webkitHTML = "<script>"
" window.addEventListener('message', (event) => {"
" event.ports[0].postMessage('got port and message ' + event.data);"
" }, false)"
"</script>"_s;
bool finishedLoading { false };
HTTPServer server({
{ "/example"_s, { exampleHTML } },
{ "/webkit"_s, { webkitHTML } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
navigationDelegate.get().didFinishNavigation = makeBlockPtr([&](WKWebView *, WKNavigation *navigation) {
if (navigation._request) {
EXPECT_WK_STREQ(navigation._request.URL.absoluteString, "https://example.com/example");
finishedLoading = true;
}
}).get();
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
__block RetainPtr<NSString> alert;
auto uiDelegate = adoptNS([TestUIDelegate new]);
uiDelegate.get().runJavaScriptAlertPanelWithMessage = ^(WKWebView *, NSString *message, WKFrameInfo *, void (^completionHandler)(void)) {
alert = message;
completionHandler();
};
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
webView.get().UIDelegate = uiDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
Util::run(&finishedLoading);
while (!alert)
Util::spinRunLoop();
EXPECT_WK_STREQ(alert.get(), "parent frame received got port and message ping");
__block bool done { false };
[webView _frames:^(_WKFrameTreeNode *mainFrame) {
pid_t mainFramePid = mainFrame.info._processIdentifier;
pid_t childFramePid = mainFrame.childFrames.firstObject.info._processIdentifier;
EXPECT_NE(mainFramePid, 0);
EXPECT_NE(childFramePid, 0);
EXPECT_NE(mainFramePid, childFramePid);
done = true;
}];
Util::run(&done);
}
TEST(SiteIsolation, PostMessageWithNotAllowedTargetOrigin)
{
auto exampleHTML = "<script>"
" onload = () => {"
" document.getElementById('webkit_frame').contentWindow.postMessage('ping', 'https://foo.org');"
" }"
"</script>"
"<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe>"_s;
auto webkitHTML = "<script>"
" window.addEventListener('message', (event) => {"
" alert('child frame received ' + event.data)"
" }, false);"
" setTimeout(() => { alert('child did not receive message'); }, 1000);"
"</script>"_s;
bool finishedLoading { false };
HTTPServer server(HTTPServer::UseCoroutines::Yes, [&](Connection connection) -> Task {
while (1) {
auto request = co_await connection.awaitableReceiveHTTPRequest();
auto path = HTTPServer::parsePath(request);
if (path == "/example"_s) {
co_await connection.awaitableSend(HTTPResponse(exampleHTML).serialize());
continue;
}
if (path == "/webkit"_s) {
co_await connection.awaitableSend(HTTPResponse(webkitHTML).serialize());
continue;
}
EXPECT_FALSE(true);
}
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
navigationDelegate.get().didFinishNavigation = makeBlockPtr([&](WKWebView *, WKNavigation *navigation) {
if (navigation._request) {
EXPECT_WK_STREQ(navigation._request.URL.absoluteString, "https://example.com/example");
finishedLoading = true;
}
}).get();
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
__block RetainPtr<NSString> alert;
auto uiDelegate = adoptNS([TestUIDelegate new]);
uiDelegate.get().runJavaScriptAlertPanelWithMessage = ^(WKWebView *, NSString *message, WKFrameInfo *, void (^completionHandler)(void)) {
alert = message;
completionHandler();
};
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
webView.get().UIDelegate = uiDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
Util::run(&finishedLoading);
while (!alert)
Util::spinRunLoop();
EXPECT_WK_STREQ(alert.get(), "child did not receive message");
__block bool done { false };
[webView _frames:^(_WKFrameTreeNode *mainFrame) {
pid_t mainFramePid = mainFrame.info._processIdentifier;
pid_t childFramePid = mainFrame.childFrames.firstObject.info._processIdentifier;
EXPECT_NE(mainFramePid, 0);
EXPECT_NE(childFramePid, 0);
EXPECT_NE(mainFramePid, childFramePid);
done = true;
}];
Util::run(&done);
}
TEST(SiteIsolation, PostMessageToIFrameWithOpaqueOrigin)
{
auto exampleHTML = "<script>"
" onload = () => {"
" try {"
" document.getElementById('webkit_frame').contentWindow.postMessage('ping', 'data:');"
" } catch (error) {"
" alert(error);"
" }"
" }"
"</script>"
"<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe>"_s;
auto webkitHTML = "<script>"
" window.addEventListener('message', (event) => {"
" alert('child frame received ' + event.data)"
" }, false);"
"</script>"_s;
bool finishedLoading { false };
HTTPServer server({
{ "/example"_s, { exampleHTML } },
{ "/webkit"_s, { webkitHTML } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
navigationDelegate.get().didFinishNavigation = makeBlockPtr([&](WKWebView *, WKNavigation *navigation) {
if (navigation._request) {
EXPECT_WK_STREQ(navigation._request.URL.absoluteString, "https://example.com/example");
finishedLoading = true;
}
}).get();
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
__block RetainPtr<NSString> alert;
auto uiDelegate = adoptNS([TestUIDelegate new]);
uiDelegate.get().runJavaScriptAlertPanelWithMessage = ^(WKWebView *, NSString *message, WKFrameInfo *, void (^completionHandler)(void)) {
alert = message;
completionHandler();
};
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
webView.get().UIDelegate = uiDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
Util::run(&finishedLoading);
while (!alert)
Util::spinRunLoop();
EXPECT_WK_STREQ(alert.get(), "SyntaxError: The string did not match the expected pattern.");
__block bool done { false };
[webView _frames:^(_WKFrameTreeNode *mainFrame) {
pid_t mainFramePid = mainFrame.info._processIdentifier;
pid_t childFramePid = mainFrame.childFrames.firstObject.info._processIdentifier;
EXPECT_NE(mainFramePid, 0);
EXPECT_NE(childFramePid, 0);
EXPECT_NE(mainFramePid, childFramePid);
done = true;
}];
Util::run(&done);
}
TEST(SiteIsolation, QueryFramesStateAfterNavigating)
{
HTTPServer server({
{ "/page1.html"_s, { "<iframe src='subframe1.html'></iframe><iframe src='subframe2.html'></iframe><iframe src='subframe3.html'></iframe>"_s } },
{ "/page2.html"_s, { "<iframe src='subframe4.html'></iframe>"_s } },
{ "/subframe1.html"_s, { "SubFrame1"_s } },
{ "/subframe2.html"_s, { "SubFrame2"_s } },
{ "/subframe3.html"_s, { "SubFrame3"_s } },
{ "/subframe4.html"_s, { "SubFrame4"_s } }
}, HTTPServer::Protocol::Http);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero]);
[webView synchronouslyLoadRequest:server.request("/page1.html"_s)];
__block bool done = false;
[webView _frames:^(_WKFrameTreeNode *mainFrame) {
EXPECT_EQ(mainFrame.childFrames.count, 3U);
done = true;
}];
Util::run(&done);
[webView synchronouslyLoadRequest:server.request("/page2.html"_s)];
done = false;
[webView _frames:^(_WKFrameTreeNode *mainFrame) {
EXPECT_EQ(mainFrame.childFrames.count, 1U);
done = true;
}];
Util::run(&done);
}
TEST(SiteIsolation, NavigatingCrossOriginIframeToSameOrigin)
{
HTTPServer server({
{ "/example"_s, { "<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe>"_s } },
{ "/example_subframe"_s, { "<script>alert('done')</script>"_s } },
{ "/webkit"_s, { "<script>window.location='https://example.com/example_subframe'</script>"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
EXPECT_WK_STREQ([webView _test_waitForAlert], "done");
__block bool done { false };
[webView _frames:^(_WKFrameTreeNode *mainFrame) {
_WKFrameTreeNode *childFrame = mainFrame.childFrames.firstObject;
pid_t mainFramePid = mainFrame.info._processIdentifier;
pid_t childFramePid = childFrame.info._processIdentifier;
EXPECT_NE(mainFramePid, 0);
EXPECT_NE(childFramePid, 0);
EXPECT_EQ(mainFramePid, childFramePid);
EXPECT_WK_STREQ(mainFrame.info.securityOrigin.host, "example.com");
EXPECT_WK_STREQ(childFrame.info.securityOrigin.host, "example.com");
done = true;
}];
Util::run(&done);
}
TEST(SiteIsolation, ParentNavigatingCrossOriginIframeToSameOrigin)
{
HTTPServer server({
{ "/example"_s, { "<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe><script>onload = () => { document.getElementById('webkit_frame').src = 'https://example.com/example_subframe' }</script>"_s } },
{ "/example_subframe"_s, { "<script>onload = ()=>{ alert('done') }</script>"_s } },
{ "/webkit"_s, { "hi"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
EXPECT_WK_STREQ([webView _test_waitForAlert], "done");
__block bool done { false };
[webView _frames:^(_WKFrameTreeNode *mainFrame) {
_WKFrameTreeNode *childFrame = mainFrame.childFrames.firstObject;
pid_t mainFramePid = mainFrame.info._processIdentifier;
pid_t childFramePid = childFrame.info._processIdentifier;
EXPECT_NE(mainFramePid, 0);
EXPECT_NE(childFramePid, 0);
EXPECT_EQ(mainFramePid, childFramePid);
EXPECT_WK_STREQ(mainFrame.info.securityOrigin.host, "example.com");
EXPECT_WK_STREQ(childFrame.info.securityOrigin.host, "example.com");
done = true;
}];
Util::run(&done);
checkFrameTreesInProcesses(webView.get(), {
{ "https://example.com"_s,
{ { "https://example.com"_s } }
}
});
}
TEST(SiteIsolation, IframeNavigatesSelfWithoutChangingOrigin)
{
HTTPServer server({
{ "/example"_s, { "<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe>"_s } },
{ "/webkit"_s, { "<script>window.location='/webkit_second'</script>"_s } },
{ "/webkit_second"_s, { "<script>alert('done')</script>"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
EXPECT_WK_STREQ([webView _test_waitForAlert], "done");
__block bool done { false };
[webView _frames:^(_WKFrameTreeNode *mainFrame) {
_WKFrameTreeNode *childFrame = mainFrame.childFrames.firstObject;
pid_t mainFramePid = mainFrame.info._processIdentifier;
pid_t childFramePid = childFrame.info._processIdentifier;
EXPECT_NE(mainFramePid, 0);
EXPECT_NE(childFramePid, 0);
EXPECT_NE(mainFramePid, childFramePid);
EXPECT_WK_STREQ(mainFrame.info.securityOrigin.host, "example.com");
EXPECT_WK_STREQ(childFrame.info.securityOrigin.host, "webkit.org");
done = true;
}];
Util::run(&done);
}
TEST(SiteIsolation, IframeWithConfirm)
{
HTTPServer server({
{ "/example"_s, { "<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe>"_s } },
{ "/webkit"_s, { "<script>confirm('confirm message')</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
EXPECT_WK_STREQ([webView _test_waitForConfirm], "confirm message");
__block bool done { false };
[webView _frames:^(_WKFrameTreeNode *mainFrame) {
_WKFrameTreeNode *childFrame = mainFrame.childFrames.firstObject;
pid_t mainFramePid = mainFrame.info._processIdentifier;
pid_t childFramePid = childFrame.info._processIdentifier;
EXPECT_NE(mainFramePid, 0);
EXPECT_NE(childFramePid, 0);
EXPECT_NE(mainFramePid, childFramePid);
EXPECT_WK_STREQ(mainFrame.info.securityOrigin.host, "example.com");
EXPECT_WK_STREQ(childFrame.info.securityOrigin.host, "webkit.org");
done = true;
}];
Util::run(&done);
}
TEST(SiteIsolation, IframeWithPrompt)
{
HTTPServer server({
{ "/example"_s, { "<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe>"_s } },
{ "/webkit"_s, { "<script>prompt('prompt message', 'default input')</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
EXPECT_WK_STREQ([webView _test_waitForPromptWithReply:@"default input"], "prompt message");
__block bool done { false };
[webView _frames:^(_WKFrameTreeNode *mainFrame) {
_WKFrameTreeNode *childFrame = mainFrame.childFrames.firstObject;
pid_t mainFramePid = mainFrame.info._processIdentifier;
pid_t childFramePid = childFrame.info._processIdentifier;
EXPECT_NE(mainFramePid, 0);
EXPECT_NE(childFramePid, 0);
EXPECT_NE(mainFramePid, childFramePid);
EXPECT_WK_STREQ(mainFrame.info.securityOrigin.host, "example.com");
EXPECT_WK_STREQ(childFrame.info.securityOrigin.host, "webkit.org");
done = true;
}];
Util::run(&done);
}
TEST(SiteIsolation, GrandchildIframe)
{
HTTPServer server({
{ "/example"_s, { "<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe>"_s } },
{ "/webkit"_s, { "<iframe onload='alert(\"grandchild loaded successfully\")' srcdoc=\"<script>window.location='https://apple.com/apple'</script>\">"_s } },
{ "/apple"_s, { ""_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
EXPECT_WK_STREQ([webView _test_waitForAlert], "grandchild loaded successfully");
}
TEST(SiteIsolation, GrandchildIframeSameOriginAsGrandparent)
{
HTTPServer server({
{ "/example"_s, { "<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe>"_s } },
{ "/webkit"_s, { "<iframe src='https://example.com/example_grandchild'></iframe>\">"_s } },
{ "/example_grandchild"_s, { "<script>alert('grandchild loaded successfully')</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
EXPECT_WK_STREQ([webView _test_waitForAlert], "grandchild loaded successfully");
checkFrameTreesInProcesses(webView.get(), {
{ "https://example.com"_s, { { RemoteFrame, { { "https://example.com"_s } } } } },
{ RemoteFrame, { { "https://webkit.org"_s, { { RemoteFrame } } } } }
});
}
TEST(SiteIsolation, ChildNavigatingToNewDomain)
{
HTTPServer server({
{ "/example"_s, { "<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe>"_s } },
{ "/example_subframe"_s, { "<script>alert('done')</script>"_s } },
{ "/webkit"_s, { "<script>window.location='https://foo.com/example_subframe'</script>"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
EXPECT_WK_STREQ([webView _test_waitForAlert], "done");
__block bool done { false };
[webView _frames:^(_WKFrameTreeNode *mainFrame) {
_WKFrameTreeNode *childFrame = mainFrame.childFrames.firstObject;
pid_t mainFramePid = mainFrame.info._processIdentifier;
pid_t childFramePid = childFrame.info._processIdentifier;
EXPECT_NE(mainFramePid, 0);
EXPECT_NE(childFramePid, 0);
EXPECT_NE(mainFramePid, childFramePid);
EXPECT_WK_STREQ(mainFrame.info.securityOrigin.host, "example.com");
EXPECT_WK_STREQ(childFrame.info.securityOrigin.host, "foo.com");
done = true;
}];
Util::run(&done);
}
TEST(SiteIsolation, ChildNavigatingToMainFrameDomain)
{
HTTPServer server({
{ "/example"_s, { "<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe>"_s } },
{ "/example_subframe"_s, { "<script>alert('done')</script>"_s } },
{ "/webkit"_s, { "<script>window.location='https://example.com/example_subframe'</script>"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
EXPECT_WK_STREQ([webView _test_waitForAlert], "done");
__block bool done { false };
[webView _frames:^(_WKFrameTreeNode *mainFrame) {
_WKFrameTreeNode *childFrame = mainFrame.childFrames.firstObject;
pid_t mainFramePid = mainFrame.info._processIdentifier;
pid_t childFramePid = childFrame.info._processIdentifier;
EXPECT_NE(mainFramePid, 0);
EXPECT_NE(childFramePid, 0);
EXPECT_EQ(mainFramePid, childFramePid);
EXPECT_WK_STREQ(mainFrame.info.securityOrigin.host, "example.com");
EXPECT_WK_STREQ(childFrame.info.securityOrigin.host, "example.com");
done = true;
}];
Util::run(&done);
}
TEST(SiteIsolation, ChildNavigatingToSameDomain)
{
HTTPServer server({
{ "/example"_s, { "<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe>"_s } },
{ "/example_subframe"_s, { "<script>alert('done')</script>"_s } },
{ "/webkit"_s, { "<script>window.location='https://webkit.org/example_subframe'</script>"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
EXPECT_WK_STREQ([webView _test_waitForAlert], "done");
__block bool done { false };
[webView _frames:^(_WKFrameTreeNode *mainFrame) {
_WKFrameTreeNode *childFrame = mainFrame.childFrames.firstObject;
pid_t mainFramePid = mainFrame.info._processIdentifier;
pid_t childFramePid = childFrame.info._processIdentifier;
EXPECT_NE(mainFramePid, 0);
EXPECT_NE(childFramePid, 0);
EXPECT_NE(mainFramePid, childFramePid);
EXPECT_WK_STREQ(mainFrame.info.securityOrigin.host, "example.com");
EXPECT_WK_STREQ(childFrame.info.securityOrigin.host, "webkit.org");
done = true;
}];
Util::run(&done);
}
TEST(SiteIsolation, ChildNavigatingToDomainLoadedOnADifferentPage)
{
HTTPServer server({
{ "/example"_s, { "<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe>"_s } },
{ "/webkit"_s, { "<script>alert('done')</script>"_s } },
{ "/foo"_s, { "<iframe id='foo'><html><body><p>Hello world.</p></body></html></iframe>"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto firstWebView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
firstWebView.get().navigationDelegate = navigationDelegate.get();
[firstWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://webkit.org/foo"]]];
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
EXPECT_WK_STREQ([webView _test_waitForAlert], "done");
__block pid_t firstFramePID = 0;
__block bool done { false };
[firstWebView _frames:^(_WKFrameTreeNode *mainFrame) {
pid_t mainFramePid = mainFrame.info._processIdentifier;
EXPECT_NE(mainFramePid, 0);
firstFramePID = mainFramePid;
EXPECT_WK_STREQ(mainFrame.info.securityOrigin.host, "webkit.org");
done = true;
}];
Util::run(&done);
done = false;
[webView _frames:^(_WKFrameTreeNode *mainFrame) {
_WKFrameTreeNode *childFrame = mainFrame.childFrames.firstObject;
pid_t mainFramePid = mainFrame.info._processIdentifier;
pid_t childFramePid = childFrame.info._processIdentifier;
EXPECT_NE(mainFramePid, 0);
EXPECT_NE(childFramePid, 0);
EXPECT_NE(mainFramePid, childFramePid);
EXPECT_NE(firstFramePID, childFramePid);
EXPECT_WK_STREQ(mainFrame.info.securityOrigin.host, "example.com");
EXPECT_WK_STREQ(childFrame.info.securityOrigin.host, "webkit.org");
done = true;
}];
Util::run(&done);
}
TEST(SiteIsolation, MainFrameWithTwoIFramesInTheSameProcess)
{
HTTPServer server({
{ "/example"_s, { "<iframe id='webkit_frame_1' src='https://webkit.org/a'></iframe><iframe id='webkit_frame_2' src='https://webkit.org/b'></iframe>"_s } },
{ "/a"_s, { "<script>alert('donea')</script>"_s } },
{ "/b"_s, { "<script>alert('doneb')</script>"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
NSString* alert1 = [webView _test_waitForAlert];
NSString* alert2 = [webView _test_waitForAlert];
if ([alert1 isEqualToString:@"donea"])
EXPECT_WK_STREQ(alert2, "doneb");
else if ([alert1 isEqualToString:@"doneb"])
EXPECT_WK_STREQ(alert2, "donea");
else
EXPECT_TRUE(false);
__block bool done { false };
[webView _frames:^(_WKFrameTreeNode *mainFrame) {
EXPECT_EQ(mainFrame.childFrames.count, 2u);
_WKFrameTreeNode *childFrame = mainFrame.childFrames.firstObject;
_WKFrameTreeNode *otherChildFrame = mainFrame.childFrames[1];
pid_t mainFramePid = mainFrame.info._processIdentifier;
pid_t childFramePid = childFrame.info._processIdentifier;
pid_t otherChildFramePid = otherChildFrame.info._processIdentifier;
EXPECT_NE(mainFramePid, 0);
EXPECT_NE(childFramePid, 0);
EXPECT_NE(otherChildFramePid, 0);
EXPECT_EQ(childFramePid, otherChildFramePid);
EXPECT_NE(mainFramePid, childFramePid);
EXPECT_WK_STREQ(mainFrame.info.securityOrigin.host, "example.com");
EXPECT_WK_STREQ(childFrame.info.securityOrigin.host, "webkit.org");
EXPECT_WK_STREQ(otherChildFrame.info.securityOrigin.host, "webkit.org");
done = true;
}];
Util::run(&done);
}
TEST(SiteIsolation, ChildBeingNavigatedToMainFrameDomainByParent)
{
HTTPServer server({
{ "/example"_s, { "<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe><script>onload = () => { document.getElementById('webkit_frame').src = 'https://example.com/example_subframe' }</script>"_s } },
{ "/example_subframe"_s, { "<script>alert('done')</script>"_s } },
{ "/webkit"_s, { "<html></html>"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
EXPECT_WK_STREQ([webView _test_waitForAlert], "done");
__block bool done { false };
__block pid_t childFramePid { 0 };
[webView _frames:^(_WKFrameTreeNode *mainFrame) {
_WKFrameTreeNode *childFrame = mainFrame.childFrames.firstObject;
pid_t mainFramePid = mainFrame.info._processIdentifier;
childFramePid = childFrame.info._processIdentifier;
EXPECT_NE(mainFramePid, 0);
EXPECT_NE(childFramePid, 0);
EXPECT_EQ(mainFramePid, childFramePid);
EXPECT_WK_STREQ(mainFrame.info.securityOrigin.host, "example.com");
EXPECT_WK_STREQ(childFrame.info.securityOrigin.host, "example.com");
done = true;
}];
Util::run(&done);
checkFrameTreesInProcesses(webView.get(), {
{ "https://example.com"_s,
{ { "https://example.com"_s } }
}
});
// FIXME: We ought to be able to use processStillRunning to verify that childFramePid stops running at this point,
// but it is currently kept running, probably because we don't delete the WebFrameProxy's m_remotePageProxy when
// navigating to the main frame's process.
}
TEST(SiteIsolation, ChildBeingNavigatedToSameDomainByParent)
{
HTTPServer server({
{ "/example"_s, { "<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe><script>onload = () => { document.getElementById('webkit_frame').src = 'https://webkit.org/example_subframe' }</script>"_s } },
{ "/example_subframe"_s, { "<script>alert('done')</script>"_s } },
{ "/webkit"_s, { "<html></html>"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
EXPECT_WK_STREQ([webView _test_waitForAlert], "done");
__block bool done { false };
[webView _frames:^(_WKFrameTreeNode *mainFrame) {
_WKFrameTreeNode *childFrame = mainFrame.childFrames.firstObject;
pid_t mainFramePid = mainFrame.info._processIdentifier;
pid_t childFramePid = childFrame.info._processIdentifier;
EXPECT_NE(mainFramePid, 0);
EXPECT_NE(childFramePid, 0);
EXPECT_NE(mainFramePid, childFramePid);
EXPECT_WK_STREQ(mainFrame.info.securityOrigin.host, "example.com");
EXPECT_WK_STREQ(childFrame.info.securityOrigin.host, "webkit.org");
done = true;
}];
Util::run(&done);
checkFrameTreesInProcesses(webView.get(), {
{ "https://example.com"_s,
{ { RemoteFrame } }
}, { RemoteFrame,
{ { "https://webkit.org"_s } }
}
});
}
TEST(SiteIsolation, ChildBeingNavigatedToNewDomainByParent)
{
auto appleHTML = "<script>"
"window.addEventListener('message', (event) => {"
" parent.window.postMessage(event.data + 'pong', { 'targetOrigin' : '*' });"
"}, false);"
"alert('apple iframe loaded')"
"</script>"_s;
HTTPServer server({
{ "/example"_s, { "<iframe id='webkit_frame' src='https://webkit.org/webkit'></iframe><script>onload = () => { document.getElementById('webkit_frame').src = 'https://apple.com/apple' }</script>"_s } },
{ "/webkit"_s, { "<html></html>"_s } },
{ "/apple"_s, { appleHTML } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
EXPECT_WK_STREQ([webView _test_waitForAlert], "apple iframe loaded");
checkFrameTreesInProcesses(webView.get(), {
{ "https://example.com"_s,
{ { RemoteFrame } }
}, { RemoteFrame,
{ { "https://apple.com"_s } }
}
});
NSString *jsCheckingPostMessageRoundTripAfterIframeProcessChange = @""
"window.addEventListener('message', (event) => {"
" alert('parent frame received ' + event.data)"
"}, false);"
"document.getElementById('webkit_frame').contentWindow.postMessage('ping', '*');";
[webView evaluateJavaScript:jsCheckingPostMessageRoundTripAfterIframeProcessChange completionHandler:nil];
EXPECT_WK_STREQ([webView _test_waitForAlert], "parent frame received pingpong");
}
TEST(SiteIsolation, IframeRedirectSameSite)
{
HTTPServer server({
{ "/example"_s, { "<iframe src='https://webkit.org/webkit'></iframe>"_s } },
{ "/webkit"_s, { 302, { { "Location"_s, "https://www.webkit.org/www_webkit"_s } }, "redirecting..."_s } },
{ "/www_webkit"_s, { "arrived!"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
[navigationDelegate waitForDidFinishNavigation];
checkFrameTreesInProcesses(webView.get(), {
{ "https://example.com"_s,
{ { RemoteFrame } }
}, { RemoteFrame,
{ { "https://www.webkit.org"_s } }
}
});
}
TEST(SiteIsolation, IframeRedirectCrossSite)
{
HTTPServer server({
{ "/example1"_s, { "<iframe src='https://webkit.org/webkit1'></iframe>"_s } },
{ "/webkit1"_s, { 302, { { "Location"_s, "https://apple.com/apple1"_s } }, "redirecting..."_s } },
{ "/apple1"_s, { "arrived!"_s } },
{ "/example2"_s, { "<iframe src='https://webkit.org/webkit2'></iframe>"_s } },
{ "/webkit2"_s, { 302, { { "Location"_s, "https://webkit.org/webkit3"_s } }, "redirecting..."_s } },
{ "/webkit3"_s, { 302, { { "Location"_s, "https://example.com/example3"_s } }, "redirecting..."_s } },
{ "/example3"_s, { "arrived!"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example1"]]];
[navigationDelegate waitForDidFinishNavigation];
checkFrameTreesInProcesses(webView.get(), {
{ "https://example.com"_s,
{ { RemoteFrame } }
}, { RemoteFrame,
{ { "https://apple.com"_s } }
}
});
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example2"]]];
[navigationDelegate waitForDidFinishNavigation];
checkFrameTreesInProcesses(webView.get(), {
{ "https://example.com"_s,
{ { "https://example.com"_s } }
}
});
}
TEST(SiteIsolation, NavigationWithIFrames)
{
HTTPServer server({
{ "/1"_s, { "<iframe src='https://domain2.com/2'></iframe>"_s } },
{ "/2"_s, { "hi!"_s } },
{ "/3"_s, { "<iframe src='https://domain4.com/4'></iframe>"_s } },
{ "/4"_s, { "<iframe src='https://domain5.com/5'></iframe>"_s } },
{ "/5"_s, { "hi!"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://domain1.com/1"]]];
[navigationDelegate waitForDidFinishNavigation];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://domain3.com/3"]]];
[navigationDelegate waitForDidFinishNavigation];
[webView goBack];
[navigationDelegate waitForDidFinishNavigation];
// FIXME: Implement CachedFrame for RemoteFrames and verify the page is resumed correctly.
}
TEST(SiteIsolation, RemoveFrames)
{
HTTPServer server({
{ "/webkit_main"_s, { "<iframe src='https://webkit.org/webkit_iframe' id='wk'></iframe><iframe src='https://example.com/example_iframe' id='ex'></iframe>"_s } },
{ "/webkit_iframe"_s, { "hi!"_s } },
{ "/example_iframe"_s, { "<iframe src='example_grandchild_frame'></iframe>"_s } },
{ "/example_grandchild_frame"_s, { "hi!"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://webkit.org/webkit_main"]]];
[navigationDelegate waitForDidFinishNavigation];
checkFrameTreesInProcesses(webView.get(), {
{ "https://webkit.org"_s,
{ { "https://webkit.org"_s }, { RemoteFrame, { { RemoteFrame } } } }
}, { RemoteFrame,
{ { RemoteFrame }, { "https://example.com"_s, { { "https://example.com"_s } } } }
}
});
__block bool removedLocalFrame { false };
[webView evaluateJavaScript:@"var frame = document.getElementById('wk');frame.parentNode.removeChild(frame);" completionHandler:^(id, NSError *error) {
removedLocalFrame = true;
}];
Util::run(&removedLocalFrame);
checkFrameTreesInProcesses(webView.get(), {
{ "https://webkit.org"_s,
{ { RemoteFrame, { { RemoteFrame } } } }
}, { RemoteFrame,
{ { "https://example.com"_s, { { "https://example.com"_s } } } }
}
});
__block bool removedRemoteFrame { false };
[webView evaluateJavaScript:@"var frame = document.getElementById('ex');frame.parentNode.removeChild(frame);" completionHandler:^(id, NSError *error) {
removedRemoteFrame = true;
}];
Util::run(&removedRemoteFrame);
checkFrameTreesInProcesses(webView.get(), {
{ "https://webkit.org"_s }
});
}
TEST(SiteIsolation, ProvisionalLoadFailure)
{
HTTPServer server({
{ "/example"_s, { "<iframe src='https://webkit.org/webkit'></iframe>"_s } },
{ "/webkit"_s, { HTTPResponse::TerminateConnection::Yes } },
{ "/apple"_s, { "hello"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
[navigationDelegate waitForDidFinishNavigation];
checkFrameTreesInProcesses(webView.get(), {
{ "https://example.com"_s, { { "https://example.com"_s } } }
});
[webView evaluateJavaScript:@"var iframe = document.createElement('iframe');document.body.appendChild(iframe);iframe.src = 'https://apple.com/apple'" completionHandler:nil];
Vector<ExpectedFrameTree> expectedFrameTreesAfterAddingApple { {
"https://example.com"_s, { { "https://example.com"_s }, { RemoteFrame } }
}, {
RemoteFrame, { { RemoteFrame }, { "https://apple.com"_s } }
} };
while (!frameTreesMatch(frameTrees(webView.get()).get(), Vector<ExpectedFrameTree> { expectedFrameTreesAfterAddingApple }))
Util::spinRunLoop();
[webView evaluateJavaScript:@"iframe.onload = alert('done');iframe.src = 'https://webkit.org/webkit'" completionHandler:nil];
EXPECT_WK_STREQ([webView _test_waitForAlert], "done");
checkFrameTreesInProcesses(webView.get(), WTFMove(expectedFrameTreesAfterAddingApple));
}
TEST(SiteIsolation, MultipleReloads)
{
HTTPServer server({
{ "/example"_s, { "<iframe src='https://webkit.org/webkit'></iframe>"_s } },
{ "/webkit"_s, { "hello"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
[navigationDelegate waitForDidFinishNavigation];
checkFrameTreesInProcesses(webView.get(), {
{ "https://example.com"_s, { { RemoteFrame } } },
{ RemoteFrame, { { "https://webkit.org"_s } } }
});
[webView reload];
Util::runFor(0.1_s);
[webView reload];
[navigationDelegate waitForDidFinishNavigation];
checkFrameTreesInProcesses(webView.get(), {
{ "https://example.com"_s, { { RemoteFrame } } },
{ RemoteFrame, { { "https://webkit.org"_s } } }
});
}
#if PLATFORM(MAC)
TEST(SiteIsolation, PropagateMouseEventsToSubframe)
{
auto mainframeHTML = "<script>"
" window.eventTypes = [];"
" window.addEventListener('message', function(event) {"
" window.eventTypes.push(event.data);"
" });"
"</script>"
"<iframe src='https://domain2.com/subframe'></iframe>"_s;
auto subframeHTML = "<script>"
" addEventListener('mousemove', window.parent.postMessage('mousemove', '*'));"
" addEventListener('mousedown', (event) => { window.parent.postMessage('mousedown,' + event.pageX + ',' + event.pageY, '*') });"
" addEventListener('mouseup', (event) => { window.parent.postMessage('mouseup,' + event.pageX + ',' + event.pageY, '*') });"
"</script>"_s;
HTTPServer server({
{ "/mainframe"_s, { mainframeHTML } },
{ "/subframe"_s, { subframeHTML } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://domain1.com/mainframe"]]];
[navigationDelegate waitForDidFinishNavigation];
CGPoint eventLocationInWindow = [webView convertPoint:CGPointMake(50, 50) toView:nil];
[webView mouseEnterAtPoint:eventLocationInWindow];
[webView mouseDownAtPoint:eventLocationInWindow simulatePressure:NO];
[webView mouseUpAtPoint:eventLocationInWindow];
[webView waitForPendingMouseEvents];
NSArray<NSString *> *eventTypes = [webView objectByEvaluatingJavaScript:@"window.eventTypes"];
EXPECT_EQ(3U, eventTypes.count);
EXPECT_WK_STREQ("mousemove", eventTypes[0]);
EXPECT_WK_STREQ("mousedown,40,40", eventTypes[1]);
EXPECT_WK_STREQ("mouseup,40,40", eventTypes[2]);
}
#endif
TEST(SiteIsolation, ShutDownFrameProcessesAfterNavigation)
{
HTTPServer server({
{ "/example"_s, { "<iframe src='https://webkit.org/webkit'></iframe>"_s } },
{ "/webkit"_s, { "hello"_s } },
{ "/apple"_s, { "hello"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
auto configuration = server.httpsProxyConfiguration();
enableSiteIsolation(configuration);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
[navigationDelegate waitForDidFinishNavigation];
pid_t iframePID = findFramePID(frameTrees(webView.get()).get(), FrameType::Remote);
checkFrameTreesInProcesses(webView.get(), {
{ "https://example.com"_s, { { RemoteFrame } } },
{ RemoteFrame, { { "https://webkit.org"_s } } }
});
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://apple.com/apple"]]];
[navigationDelegate waitForDidFinishNavigation];
checkFrameTreesInProcesses(webView.get(), { { "https://apple.com"_s } });
while (processStillRunning(iframePID))
Util::spinRunLoop();
}
}