blob: 5728a1e27f15a454bf9001b8be0eea1817c27a92 [file] [log] [blame] [edit]
/*
* Copyright (C) 2025 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 "HTTPServer.h"
#import "PlatformUtilities.h"
#import "Test.h"
#import "TestNavigationDelegate.h"
#import "TestWKWebView.h"
#import <WebKit/WKContentWorldPrivate.h>
#import <WebKit/WKJSHandle.h>
#import <WebKit/WKWebpagePreferencesPrivate.h>
#import <WebKit/_WKContentWorldConfiguration.h>
#import <WebKit/_WKFeature.h>
namespace TestWebKitAPI {
static WKFrameInfo *getWindowFrameInfo(RetainPtr<WKJSHandle> node)
{
EXPECT_TRUE([node isKindOfClass:WKJSHandle.class]);
__block RetainPtr<WKFrameInfo> frame;
__block bool done { false };
[node windowProxyFrameInfo:^(WKFrameInfo *info) {
frame = info;
done = true;
}];
Util::run(&done);
return frame.autorelease();
}
TEST(JSHandle, Basic)
{
HTTPServer server({
{ "/example"_s, { "<iframe id=onlyframe src='https://webkit.org/webkit'></iframe><div id=onlydiv></div>"_s } },
{ "/webkit"_s, { "hi"_s } },
}, HTTPServer::Protocol::HttpsProxy);
RetainPtr configuration = server.httpsProxyConfiguration();
RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
RetainPtr navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
webView.get().navigationDelegate = navigationDelegate.get();
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
[navigationDelegate waitForDidFinishNavigation];
RetainPtr worldConfiguration = adoptNS([_WKContentWorldConfiguration new]);
worldConfiguration.get().jsHandleCreationEnabled = YES;
RetainPtr world = [WKContentWorld _worldWithConfiguration:worldConfiguration.get()];
RetainPtr<id> result = [webView objectByEvaluatingJavaScript:@"window.webkit.createJSHandle(onlyframe.contentWindow)" inFrame:nil inContentWorld:world.get()];
EXPECT_WK_STREQ(getWindowFrameInfo(result).request.URL.absoluteString, "https://webkit.org/webkit");
RetainPtr<WKJSHandle> iframeRef = [webView objectByEvaluatingJavaScript:@"window.webkit.createJSHandle(onlyframe)" inFrame:nil inContentWorld:world.get()];
result = [webView objectByEvaluatingJavaScript:@"window.webkit.createJSHandle(onlydiv)" inFrame:nil inContentWorld:world.get()];
EXPECT_NULL(getWindowFrameInfo(result));
{
__block bool done { false };
[webView evaluateJavaScript:@"window.webkit.createJSHandle(5)" inFrame:nil inContentWorld:world.get() completionHandler:^(id result, NSError *error) {
EXPECT_NULL(result);
EXPECT_NOT_NULL(error);
done = true;
}];
Util::run(&done);
}
result = [webView objectByEvaluatingJavaScript:@"window.webkit.createJSHandle(document.createTextNode('hi'))" inFrame:nil inContentWorld:world.get()];
EXPECT_NULL(getWindowFrameInfo(result));
EXPECT_EQ([result world], world.get());
result = [webView objectByEvaluatingJavaScript:@"window.WebKitJSHandle"];
EXPECT_NULL(result);
result = [webView objectByCallingAsyncFunction:@"return {'arg':window.webkit.createJSHandle(onlydiv)}" withArguments:nil inFrame:nil inContentWorld:world.get()];
result = [webView objectByCallingAsyncFunction:@"return arg.outerHTML" withArguments:result.get() inFrame:nil inContentWorld:world.get()];
EXPECT_WK_STREQ(result.get(), "<div id=\"onlydiv\"></div>");
result = [webView objectByCallingAsyncFunction:@"return n === undefined" withArguments:@{ @"n" : iframeRef.get() } inFrame:[webView firstChildFrame] inContentWorld:WKContentWorld.pageWorld];
EXPECT_EQ(result.get(), @YES);
result = [webView objectByCallingAsyncFunction:@"return n.id" withArguments:@{ @"n" : iframeRef.get() } inFrame:nil inContentWorld:world.get()];
EXPECT_WK_STREQ(result.get(), "onlyframe");
result = [webView objectByEvaluatingJavaScript:@"function returnThirty() { return '30'; }; window.webkit.createJSHandle(returnThirty)" inFrame:nil inContentWorld:world.get()];
EXPECT_WK_STREQ([webView objectByCallingAsyncFunction:@"return n()" withArguments:@{ @"n" : result.get() } inFrame:nil inContentWorld:world.get()], "30");
result = [webView objectByEvaluatingJavaScript:@"window.webkit.createJSHandle(window.parent)" inFrame:[webView firstChildFrame] inContentWorld:world.get()];
EXPECT_WK_STREQ(getWindowFrameInfo(result).request.URL.absoluteString, "https://example.com/example");
}
TEST(JSHandle, Equality)
{
RetainPtr worldConfiguration = adoptNS([_WKContentWorldConfiguration new]);
worldConfiguration.get().jsHandleCreationEnabled = YES;
RetainPtr world = [WKContentWorld _worldWithConfiguration:worldConfiguration.get()];
RetainPtr webView = adoptNS([TestWKWebView new]);
EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"let a = {'key':'value'}; let b = window.webkit.createJSHandle(a); let c = window.webkit.createJSHandle(a); b === c" inFrame:nil inContentWorld:world.get()] boolValue]);
WKJSHandle *b = [webView objectByEvaluatingJavaScript:@"b" inFrame:nil inContentWorld:world.get()];
WKJSHandle *c = [webView objectByEvaluatingJavaScript:@"c" inFrame:nil inContentWorld:world.get()];
WKJSHandle *d = [webView objectByEvaluatingJavaScript:@"let d = {}; window.webkit.createJSHandle(d)" inFrame:nil inContentWorld:world.get()];
EXPECT_TRUE([b isKindOfClass:WKJSHandle.class]);
EXPECT_TRUE([c isKindOfClass:WKJSHandle.class]);
EXPECT_TRUE([d isKindOfClass:WKJSHandle.class]);
EXPECT_NE(b, c);
EXPECT_NE(b, d);
EXPECT_NE(c, d);
EXPECT_TRUE([b isEqual:c]);
EXPECT_FALSE([b isEqual:d]);
NSMutableArray<WKJSHandle *> *array = [NSMutableArray arrayWithCapacity:2];
[array addObject:b];
[array addObject:d];
[array addObject:c];
EXPECT_EQ(array.count, 3u);
[array removeObject:b];
EXPECT_EQ(array.count, 1u);
NSSet<WKJSHandle *> *set = [NSSet setWithObjects:b, c, d, nil];
EXPECT_EQ(set.count, 2u);
NSDictionary<NSString *, WKJSHandle *> *dictionary = @{
@"b" : b,
@"c" : c,
@"d" : d
};
EXPECT_EQ(dictionary[@"b"], b);
EXPECT_EQ(dictionary[@"c"], c);
EXPECT_EQ(dictionary[@"d"], d);
EXPECT_EQ(dictionary.count, 3u);
EXPECT_WK_STREQ([webView objectByCallingAsyncFunction:@"return b.key" withArguments:@{ @"b" : b } inFrame:nil inContentWorld:world.get()], "value");
worldConfiguration.get().name = @"otherworldly";
RetainPtr otherWorld = [WKContentWorld _worldWithConfiguration:worldConfiguration.get()];
EXPECT_NE(world.get(), otherWorld.get());
EXPECT_TRUE([[webView objectByCallingAsyncFunction:@"return b === undefined" withArguments:@{ @"b" : b } inFrame:nil inContentWorld:otherWorld.get()] boolValue]);
}
TEST(JSHandle, WebpagePreferences)
{
HTTPServer server({
{ "/example"_s, { "<iframe src='https://webkit.org/webkit'></iframe>"_s } },
{ "/webkit"_s, { "hi"_s } },
}, HTTPServer::Protocol::HttpsProxy);
RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:server.httpsProxyConfiguration()]);
RetainPtr navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate allowAnyTLSCertificate];
webView.get().navigationDelegate = navigationDelegate.get();
__block bool allow = false;
navigationDelegate.get().decidePolicyForNavigationActionWithPreferences = ^(WKNavigationAction *, WKWebpagePreferences *preferences, void (^completionHandler)(WKNavigationActionPolicy, WKWebpagePreferences *)) {
preferences._allowsJSHandleCreationInPageWorld = allow;
completionHandler(WKNavigationActionPolicyAllow, preferences);
};
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]];
NSString *checkClass = @"window.WebKitJSHandle === undefined";
NSString *checkWindowWebKit = @"window.webkit === undefined || window.webkit.createJSHandle === undefined";
[webView loadRequest:request];
[navigationDelegate waitForDidFinishNavigation];
EXPECT_TRUE([[webView objectByEvaluatingJavaScript:checkClass] boolValue]);
EXPECT_TRUE([[webView objectByEvaluatingJavaScript:checkWindowWebKit] boolValue]);
allow = true;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.org/example"]]];
[navigationDelegate waitForDidFinishNavigation];
EXPECT_FALSE([[webView objectByEvaluatingJavaScript:checkClass] boolValue]);
EXPECT_FALSE([[webView objectByEvaluatingJavaScript:checkWindowWebKit] boolValue]);
EXPECT_EQ([[webView objectByEvaluatingJavaScript:@"window.webkit.createJSHandle(()=>{})"] world], WKContentWorld.pageWorld);
allow = false;
[webView loadRequest:request];
[navigationDelegate waitForDidFinishNavigation];
EXPECT_TRUE([[webView objectByEvaluatingJavaScript:checkClass] boolValue]);
EXPECT_TRUE([[webView objectByEvaluatingJavaScript:checkWindowWebKit] boolValue]);
allow = true;
[webView loadRequest:request];
[navigationDelegate waitForDidFinishNavigation];
EXPECT_FALSE([[webView objectByEvaluatingJavaScript:checkClass] boolValue]);
EXPECT_FALSE([[webView objectByEvaluatingJavaScript:checkWindowWebKit] boolValue]);
}
TEST(JSHandle, Reuse)
{
RetainPtr worldConfiguration = adoptNS([_WKContentWorldConfiguration new]);
worldConfiguration.get().jsHandleCreationEnabled = YES;
RetainPtr world = [WKContentWorld _worldWithConfiguration:worldConfiguration.get()];
RetainPtr webView = adoptNS([TestWKWebView new]);
@autoreleasepool {
[webView objectByEvaluatingJavaScript:@"let f = window.webkit.createJSHandle(()=>{ return 42; }); f" inFrame:nil inContentWorld:world.get()];
}
WKJSHandle *fun = [webView objectByEvaluatingJavaScript:@"f" inFrame:nil inContentWorld:world.get()];
EXPECT_TRUE([[webView objectByCallingAsyncFunction:@"return fun()" withArguments:@{ @"fun":fun } inFrame:nil inContentWorld:world.get()] isEqual:@42]);
}
}