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

#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/test/bind_test_util.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_paths.h"
#include "content/public/test/content_browser_test.h"
#include "content/shell/browser/shell.h"
#include "services/content/public/cpp/navigable_contents.h"
#include "services/content/public/mojom/constants.mojom.h"
#include "services/content/public/mojom/navigable_contents_factory.mojom.h"
#include "services/service_manager/public/cpp/connector.h"

namespace content {
namespace {

class ContentServiceBrowserTest : public ContentBrowserTest {
 public:
  ContentServiceBrowserTest() = default;
  ~ContentServiceBrowserTest() override = default;

  void SetUpOnMainThread() override {
    ContentBrowserTest::SetUpOnMainThread();

    base::FilePath test_data_path;
    CHECK(base::PathService::Get(DIR_TEST_DATA, &test_data_path));
    embedded_test_server()->ServeFilesFromDirectory(test_data_path);
    CHECK(embedded_test_server()->Start());
  }

 protected:
  content::mojom::NavigableContentsFactory* GetFactory() {
    if (!factory_) {
      auto* connector = BrowserContext::GetConnectorFor(
          shell()->web_contents()->GetBrowserContext());
      connector->BindInterface(content::mojom::kServiceName, &factory_);
    }
    return factory_.get();
  }

 private:
  content::mojom::NavigableContentsFactoryPtr factory_;

  DISALLOW_COPY_AND_ASSIGN(ContentServiceBrowserTest);
};

class StopLoadingObserver : public content::NavigableContentsObserver {
 public:
  StopLoadingObserver() = default;
  ~StopLoadingObserver() override = default;

  void CallOnNextStopLoading(base::OnceClosure callback) {
    callback_ = std::move(callback);
  }

 private:
  // content::NavigableContentsObserver:
  void DidStopLoading() override {
    if (callback_)
      std::move(callback_).Run();
  }

  base::OnceClosure callback_;

  DISALLOW_COPY_AND_ASSIGN(StopLoadingObserver);
};

class NavigationObserver : public content::NavigableContentsObserver {
 public:
  NavigationObserver() = default;
  ~NavigationObserver() override = default;

  void CallOnNextNavigation(base::OnceClosure callback) {
    callback_ = std::move(callback);
  }

  size_t navigations_finished() const { return navigations_finished_; }
  const GURL& last_url() const { return last_url_; }

 private:
  void DidFinishNavigation(
      const GURL& url,
      bool is_main_frame,
      bool is_error_page,
      const net::HttpResponseHeaders* response_headers) override {
    ++navigations_finished_;
    last_url_ = url;
    if (callback_)
      std::move(callback_).Run();
  }

  base::OnceClosure callback_;
  size_t navigations_finished_ = 0;
  GURL last_url_;

  DISALLOW_COPY_AND_ASSIGN(NavigationObserver);
};

class NavigationSuppressedObserver : public content::NavigableContentsObserver {
 public:
  NavigationSuppressedObserver() = default;
  ~NavigationSuppressedObserver() override = default;

  void CallOnNextNavigationSuppression(base::OnceClosure callback) {
    callback_ = std::move(callback);
  }

  size_t navigations_suppressed() const { return navigations_suppressed_; }
  const GURL& last_url() const { return last_url_; }

 private:
  void DidSuppressNavigation(const GURL& url,
                             WindowOpenDisposition disposition,
                             bool from_user_gesture) override {
    ++navigations_suppressed_;
    last_url_ = url;
    if (callback_)
      std::move(callback_).Run();
  }

  base::OnceClosure callback_;
  size_t navigations_suppressed_ = 0;
  GURL last_url_;

  DISALLOW_COPY_AND_ASSIGN(NavigationSuppressedObserver);
};

// Verifies that the embedded Content Service is reachable. Does a basic
// end-to-end sanity check to also verify that a NavigableContents is backed by
// a WebContents instance in the browser.
IN_PROC_BROWSER_TEST_F(ContentServiceBrowserTest, EmbeddedContentService) {
  auto contents = std::make_unique<NavigableContents>(GetFactory());

  const GURL kTestUrl = embedded_test_server()->GetURL("/hello.html");
  StopLoadingObserver observer;
  base::RunLoop loop;
  observer.CallOnNextStopLoading(loop.QuitClosure());
  contents->AddObserver(&observer);
  contents->Navigate(kTestUrl);
  loop.Run();
  contents->RemoveObserver(&observer);
}

IN_PROC_BROWSER_TEST_F(ContentServiceBrowserTest, DidFinishNavigation) {
  auto contents = std::make_unique<NavigableContents>(GetFactory());

  const GURL kTestUrl = embedded_test_server()->GetURL("/hello.html");
  NavigationObserver observer;
  base::RunLoop loop;
  observer.CallOnNextNavigation(loop.QuitClosure());
  contents->AddObserver(&observer);
  contents->Navigate(kTestUrl);

  EXPECT_EQ(0u, observer.navigations_finished());

  loop.Run();
  contents->RemoveObserver(&observer);

  EXPECT_EQ(1u, observer.navigations_finished());
  EXPECT_EQ(kTestUrl, observer.last_url());
}

IN_PROC_BROWSER_TEST_F(ContentServiceBrowserTest, SuppressNavigations) {
  auto params = mojom::NavigableContentsParams::New();
  params->suppress_navigations = true;
  auto contents =
      std::make_unique<NavigableContents>(GetFactory(), std::move(params));

  NavigationObserver navigation_observer;

  NavigationSuppressedObserver suppressed_observer;
  base::RunLoop suppressed_loop;
  suppressed_observer.CallOnNextNavigationSuppression(
      suppressed_loop.QuitClosure());

  contents->AddObserver(&navigation_observer);
  contents->AddObserver(&suppressed_observer);

  // This URL is expected to elicit an automatic navigation on load, which
  // should be suppressed because |suppress_navigations| is |true|.
  const GURL kTestUrl =
      embedded_test_server()->GetURL("/navigate_on_load.html");
  contents->Navigate(kTestUrl);

  EXPECT_EQ(0u, navigation_observer.navigations_finished());
  EXPECT_EQ(0u, suppressed_observer.navigations_suppressed());

  suppressed_loop.Run();

  EXPECT_EQ(1u, navigation_observer.navigations_finished());
  EXPECT_EQ(1u, suppressed_observer.navigations_suppressed());

  EXPECT_EQ(kTestUrl, navigation_observer.last_url());
  EXPECT_EQ(embedded_test_server()->GetURL("/hello.html"),
            suppressed_observer.last_url());

  // Now force a navigation on the same contents. It should complete and we can
  // verify that only two navigations (the initial explicit navigation above,
  // and the one we do here) were completed. This effectively confirms that the
  // scripted navigation to "/hello.html" was fully suppressed.
  const GURL kTestUrl2 = embedded_test_server()->GetURL("/title1.html");
  contents->Navigate(kTestUrl2);

  base::RunLoop navigation_loop;
  navigation_observer.CallOnNextNavigation(navigation_loop.QuitClosure());
  navigation_loop.Run();

  contents->RemoveObserver(&navigation_observer);
  contents->RemoveObserver(&suppressed_observer);

  EXPECT_EQ(2u, navigation_observer.navigations_finished());
  EXPECT_EQ(kTestUrl2, navigation_observer.last_url());
}

}  // namespace
}  // namespace content
