// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell_content_browser_client.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::Optional;

namespace content {

namespace {
const char kTestHeaders[] = "HTTP/1.1 200 OK\nContent-type: text/html\n\n";
}  // namespace

using TopFramePopulationBrowsertest = ContentBrowserTest;

// Test that the top frame origin field is populated on subresource requests
// from a top frame.
IN_PROC_BROWSER_TEST_F(TopFramePopulationBrowsertest, FromTopFrame) {
  int number_of_frame_loaders = 0;
  bool attempted_to_load_image = false;
  ShellContentBrowserClient::Get()->set_url_loader_factory_params_callback(
      base::BindLambdaForTesting(
          [&](const network::mojom::URLLoaderFactoryParams* params,
              const url::Origin& unused_origin,
              bool unused_is_for_isolated_world,
              bool unused_is_for_service_worker) {
            ASSERT_TRUE(params);

            // Ignore URLLoaderFactoryParams for the initial empty document.
            if (params->isolation_info.top_frame_origin()->opaque())
              return;

            ASSERT_THAT(params->isolation_info.top_frame_origin(),
                        Optional(url::Origin::Create(GURL("http://main.com"))));
            ++number_of_frame_loaders;
          }));

  // Serve a page from which the renderer will make a subresource
  // request, in order to observe this request's top frame origin and verify
  // that it is correct.
  URLLoaderInterceptor interceptor(base::BindLambdaForTesting(
      [&attempted_to_load_image](URLLoaderInterceptor::RequestParams* params) {
        std::string spec = params->url_request.url.spec();

        if (spec.find("main") != std::string::npos) {
          URLLoaderInterceptor::WriteResponse(
              kTestHeaders,
              R"(<html><img src="http://www.image.com/image.png"></html>)",
              params->client.get());

          return true;
        }
        if (spec.find("image")) {
          attempted_to_load_image = true;
        }
        return false;
      }));

  EXPECT_TRUE(NavigateToURL(shell(), GURL("http://main.com/")));

  // As a sanity check, make sure the test did actually try to load the
  // subresource.
  ASSERT_TRUE(attempted_to_load_image);
  ASSERT_EQ(number_of_frame_loaders, 1);
}

// Test that the top frame origin field is populated on subresource requests
// from a nested frame.
IN_PROC_BROWSER_TEST_F(TopFramePopulationBrowsertest, FromNestedFrame) {
  int number_of_frame_loaders = 0;
  bool attempted_to_load_image = false;
  ShellContentBrowserClient::Get()->set_url_loader_factory_params_callback(
      base::BindLambdaForTesting(
          [&](const network::mojom::URLLoaderFactoryParams* params,
              const url::Origin& unused_origin,
              bool unused_is_for_isolated_world,
              bool unused_is_for_service_worker) {
            ASSERT_TRUE(params);

            // Ignore URLLoaderFactoryParams for the initial empty document.
            if (params->isolation_info.top_frame_origin()->opaque())
              return;

            ASSERT_THAT(params->isolation_info.top_frame_origin(),
                        Optional(url::Origin::Create(GURL("http://main.com"))));
            ++number_of_frame_loaders;
          }));

  // Serve a page with a nested cross-origin frame in order to, when the
  // renderer makes a subresource request from this nested frame, verify that
  // the request's top frame origin correctly equals the top frame's origin
  // (instead of, say, the nested frame's origin).
  URLLoaderInterceptor interceptor(base::BindLambdaForTesting(
      [&attempted_to_load_image](URLLoaderInterceptor::RequestParams* params) {
        std::string spec = params->url_request.url.spec();

        if (spec.find("main") != std::string::npos) {
          URLLoaderInterceptor::WriteResponse(kTestHeaders, R"(<html><iframe
              src="http://frame.com/"></html>)",
                                              params->client.get());

          return true;
        }
        if (spec.find("frame") != std::string::npos) {
          URLLoaderInterceptor::WriteResponse(
              kTestHeaders,
              R"(<html><img src="http://www.image.com/image.png"></html>)",
              params->client.get());

          return true;
        }
        if (spec.find("image")) {
          attempted_to_load_image = true;
        }
        return false;
      }));

  EXPECT_TRUE(NavigateToURL(shell(), GURL("http://main.com/")));

  // As a sanity check, make sure the test did actually try to load the
  // subresource.
  ASSERT_TRUE(attempted_to_load_image);
  ASSERT_EQ(number_of_frame_loaders, 2);
}

}  // namespace content
