chromium/ios/chrome/browser/permissions/model/geolocation_overrides_javascript_test.mm

// 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 kClearWatchPageUrl[] = "/clearWatch";
const char kGetCurrentPositionPageUrl[] = "/getCurrentPosition";
const char kWatchPositionPageUrl[] = "/watchPosition";

const char kClearWatchPageHtml[] = "<html><body><script>"
                                   "navigator.geolocation.clearWatch("
                                   ");"
                                   "</script></body></html>";
const char kGetCurrentPositionPageHtml[] =
    "<html><body><script>"
    "navigator.geolocation.getCurrentPosition((position) => {});"
    "</script></body></html>";
const char kWatchPositionPageHtml[] =
    "<html><body><script>"
    "function doNothing(arg) {}"
    "watchId = navigator.geolocation.watchPosition(doNothing, doNothing, {});"
    "</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 == kClearWatchPageUrl) {
    http_response->set_content(kClearWatchPageHtml);
  } else if (request.relative_url == kGetCurrentPositionPageUrl) {
    http_response->set_content(kGetCurrentPositionPageHtml);
  } else if (request.relative_url == kWatchPositionPageUrl) {
    http_response->set_content(kWatchPositionPageHtml);
  } else {
    return nullptr;
  }
  return std::move(http_response);
}
}  // namespace

static NSString* kMessageHandlerResponseApiKey = @"api";
static NSString* kMessageHandlerResponseApiValueClearWatch = @"clearWatch";
static NSString* kMessageHandlerResponseApiValueGetPosition =
    @"getCurrentPosition";
static NSString* kMessageHandlerResponseApiValueWatch = @"watchPosition";

@interface GeolocationScriptMessageHandler : NSObject <WKScriptMessageHandler>
@property(nonatomic, strong) WKScriptMessage* lastReceivedMessage;
@end

@implementation GeolocationScriptMessageHandler

- (void)configureForWebView:(WKWebView*)webView {
  [webView.configuration.userContentController
      addScriptMessageHandler:self
                         name:@"GeolocationAPIAccessedHandler"];
}

- (void)userContentController:(WKUserContentController*)userContentController
      didReceiveScriptMessage:(WKScriptMessage*)message {
  self.lastReceivedMessage = message;
}

@end

// Test fixture for geolocation_overrides.ts.
class GeolocationOverridesJavaScriptTest : public web::JavascriptTest {
 protected:
  GeolocationOverridesJavaScriptTest()
      : server_(net::EmbeddedTestServer::TYPE_HTTP),
        message_handler_([[GeolocationScriptMessageHandler alloc] init]) {}
  ~GeolocationOverridesJavaScriptTest() override {}

  void SetUp() override {
    JavascriptTest::SetUp();

    AddUserScript(@"geolocation_overrides");

    server_.RegisterRequestHandler(base::BindRepeating(&StandardResponse));
    ASSERT_TRUE(server_.Start());

    [message_handler_ configureForWebView:web_view()];
  }

  const net::EmbeddedTestServer& server() { return server_; }

  GeolocationScriptMessageHandler* message_handler() {
    return message_handler_;
  }

 private:
  net::EmbeddedTestServer server_;
  GeolocationScriptMessageHandler* message_handler_;
};

// Tests that the correct message is received when the clearWatch API is called.
TEST_F(GeolocationOverridesJavaScriptTest,
       GeolocationClearWatchMessageReceived) {
  GURL URL = server().GetURL(kClearWatchPageUrl);
  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(1ul, allKeys.count);
  EXPECT_TRUE([allKeys containsObject:kMessageHandlerResponseApiKey]);
  EXPECT_NSEQ(kMessageHandlerResponseApiValueClearWatch,
              body[kMessageHandlerResponseApiKey]);
}

// Tests that the correct message is received when the getCurrentPosition API is
// called.
TEST_F(GeolocationOverridesJavaScriptTest,
       GeolocationGetCurrentPositionMessageReceived) {
  GURL URL = server().GetURL(kGetCurrentPositionPageUrl);
  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(1ul, allKeys.count);
  EXPECT_TRUE([allKeys containsObject:kMessageHandlerResponseApiKey]);
  EXPECT_NSEQ(kMessageHandlerResponseApiValueGetPosition,
              body[kMessageHandlerResponseApiKey]);
}

// Tests that the correct message is received when the watchPosition API is
// called.
TEST_F(GeolocationOverridesJavaScriptTest,
       GeolocationWatchPositionMessageReceived) {
  GURL URL = server().GetURL(kWatchPositionPageUrl);
  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(1ul, allKeys.count);
  EXPECT_TRUE([allKeys containsObject:kMessageHandlerResponseApiKey]);
  EXPECT_NSEQ(kMessageHandlerResponseApiValueWatch,
              body[kMessageHandlerResponseApiKey]);
}