// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "base/test/ios/wait_util.h"
#import "ios/web/public/test/javascript_test.h"
#import "ios/web/public/test/js_test_util.h"
#import "net/test/embedded_test_server/embedded_test_server.h"
#import "testing/gtest_mac.h"
namespace {
const char kEmptyPageUrl[] = "/blank";
const char kGetUserMediaAudioPageUrl[] = "/getUserMediaAudio";
const char kGetUserMediaAudioVideoPageUrl[] = "/getUserMediaAudioVideo";
const char kGetUserMediaVideoPageUrl[] = "/getUserMediaVideo";
const char kGetUserMediaAudioPageHtml[] =
"<html><body><script>"
"navigator.mediaDevices.getUserMedia({ audio : true });"
"</script></body></html>";
const char kGetUserMediaAudioVideoPageHtml[] =
"<html><body><script>"
"navigator.mediaDevices.getUserMedia({ audio : true, video : true });"
"</script></body></html>";
const char kGetUserMediaVideoPageHtml[] =
"<html><body><script>"
"navigator.mediaDevices.getUserMedia({ video : true });"
"</script></body></html>";
// Provides responses for initial page and destination URLs.
std::unique_ptr<net::test_server::HttpResponse> StandardResponse(
const net::test_server::HttpRequest& request) {
std::unique_ptr<net::test_server::BasicHttpResponse> http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
if (request.relative_url == kEmptyPageUrl) {
http_response->set_content("<html><body></body></html>");
} else if (request.relative_url == kGetUserMediaAudioPageUrl) {
http_response->set_content(kGetUserMediaAudioPageHtml);
} else if (request.relative_url == kGetUserMediaAudioVideoPageUrl) {
http_response->set_content(kGetUserMediaAudioVideoPageHtml);
} else if (request.relative_url == kGetUserMediaVideoPageUrl) {
http_response->set_content(kGetUserMediaVideoPageHtml);
} else {
return nullptr;
}
return std::move(http_response);
}
} // namespace
static NSString* kMessageHandlerResponseAudioKey = @"audio";
static NSString* kMessageHandlerResponseVideoKey = @"video";
@interface MediaScriptMessageHandler : NSObject <WKScriptMessageHandler>
@property(nonatomic, strong) WKScriptMessage* lastReceivedMessage;
@end
@implementation MediaScriptMessageHandler
- (void)configureForWebView:(WKWebView*)webView {
[webView.configuration.userContentController
addScriptMessageHandler:self
name:@"MediaAPIAccessedHandler"];
}
- (void)userContentController:(WKUserContentController*)userContentController
didReceiveScriptMessage:(WKScriptMessage*)message {
self.lastReceivedMessage = message;
}
@end
// Test fixture for media_overrides.ts.
class MediaOverridesJavaScriptTest : public web::JavascriptTest {
protected:
MediaOverridesJavaScriptTest()
: server_(net::EmbeddedTestServer::TYPE_HTTP),
message_handler_([[MediaScriptMessageHandler alloc] init]) {}
~MediaOverridesJavaScriptTest() override {}
void SetUp() override {
JavascriptTest::SetUp();
AddUserScript(@"media_overrides");
server_.RegisterRequestHandler(base::BindRepeating(&StandardResponse));
ASSERT_TRUE(server_.Start());
[message_handler_ configureForWebView:web_view()];
}
const net::EmbeddedTestServer& server() { return server_; }
MediaScriptMessageHandler* message_handler() { return message_handler_; }
private:
net::EmbeddedTestServer server_;
MediaScriptMessageHandler* message_handler_;
};
// Tests that `navigator.mediaDevices` API is not added for insecure pages where
// the API does not already exist. (Pages loaded with LoadHtml are not
// considered secure within WebKit.)
TEST_F(MediaOverridesJavaScriptTest, MediaAPINotAddedForInsecureContexts) {
ASSERT_TRUE(LoadHtml(@"<html><head></head><body></body></html>"));
id result = web::test::ExecuteJavaScript(web_view(),
@"typeof navigator.mediaDevices");
EXPECT_NSEQ(@"undefined", result);
}
// Tests that the original `getMediaDevices` method is still called by checking
// that it returns a promise.
TEST_F(MediaOverridesJavaScriptTest, MediaOverrideCallsOriginal) {
ASSERT_TRUE(LoadUrl(server().GetURL(kEmptyPageUrl)));
id result = web::test::ExecuteJavaScript(
web_view(), @"const promise = navigator.mediaDevices.getUserMedia({ "
@"audio : true }); typeof promise.then;");
EXPECT_NSEQ(@"function", result);
}
// Tests that the correct message is received for an audio media request.
TEST_F(MediaOverridesJavaScriptTest, WebKitAudioMessageReceived) {
GURL URL = server().GetURL(kGetUserMediaAudioPageUrl);
ASSERT_TRUE(LoadUrl(URL));
EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForPageLoadTimeout, ^{
return message_handler().lastReceivedMessage != nil;
}));
NSDictionary* body = message_handler().lastReceivedMessage.body;
NSArray* allKeys = body.allKeys;
EXPECT_EQ(2ul, allKeys.count);
EXPECT_TRUE([allKeys containsObject:kMessageHandlerResponseAudioKey]);
EXPECT_TRUE([allKeys containsObject:kMessageHandlerResponseVideoKey]);
EXPECT_TRUE([body[kMessageHandlerResponseAudioKey] boolValue]);
EXPECT_FALSE([body[kMessageHandlerResponseVideoKey] boolValue]);
}
// Tests that the correct message is received for an audio and video media
// request.
TEST_F(MediaOverridesJavaScriptTest, WebKitAudioVideoMessageReceived) {
GURL URL = server().GetURL(kGetUserMediaAudioVideoPageUrl);
ASSERT_TRUE(LoadUrl(URL));
EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForPageLoadTimeout, ^{
return message_handler().lastReceivedMessage != nil;
}));
NSDictionary* body = message_handler().lastReceivedMessage.body;
NSArray* allKeys = body.allKeys;
EXPECT_EQ(2ul, allKeys.count);
EXPECT_TRUE([allKeys containsObject:kMessageHandlerResponseAudioKey]);
EXPECT_TRUE([allKeys containsObject:kMessageHandlerResponseVideoKey]);
EXPECT_TRUE([body[kMessageHandlerResponseAudioKey] boolValue]);
EXPECT_TRUE([body[kMessageHandlerResponseVideoKey] boolValue]);
}
// Tests that the correct message is received for a video media request.
TEST_F(MediaOverridesJavaScriptTest, WebKitVideoMessageReceived) {
GURL URL = server().GetURL(kGetUserMediaVideoPageUrl);
ASSERT_TRUE(LoadUrl(URL));
EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForPageLoadTimeout, ^{
return message_handler().lastReceivedMessage != nil;
}));
NSDictionary* body = message_handler().lastReceivedMessage.body;
NSArray* allKeys = body.allKeys;
EXPECT_EQ(2ul, allKeys.count);
EXPECT_TRUE([allKeys containsObject:kMessageHandlerResponseAudioKey]);
EXPECT_TRUE([allKeys containsObject:kMessageHandlerResponseVideoKey]);
EXPECT_FALSE([body[kMessageHandlerResponseAudioKey] boolValue]);
EXPECT_TRUE([body[kMessageHandlerResponseVideoKey] boolValue]);
}