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

#include <string>

#include "base/command_line.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/permissions/test/permission_request_observer.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"

namespace {

// Handles HTTP requests to |path| with |content| as the response body.
// |content| is expected to be JavaScript; the response mime type is always set
// to "text/javascript".
// Invokes |done_callback| after serving the HTTP request.
std::unique_ptr<net::test_server::HttpResponse> RespondWithJS(
    const std::string& path,
    const std::string& content,
    base::OnceClosure done_callback,
    const net::test_server::HttpRequest& request) {
  GURL request_url = request.GetURL();
  if (request_url.GetPath() != path) {
    return nullptr;
  }

  auto response = std::make_unique<net::test_server::BasicHttpResponse>();
  response->set_content_type("text/javascript");
  response->set_content(content);
  std::move(done_callback).Run();
  return response;
}

}  // namespace

class WakeLockBrowserTest : public InProcessBrowserTest {
 protected:
  // InProcessBrowserTest:
  void SetUpCommandLine(base::CommandLine* command_line) override;

  // Shorthand for starting the embedded web server and navigating to
  // simple.html.
  // Tests calling this usually call content::EvalJs() afterwards to run custom
  // code on the dummy page.
  void NavigateToSimplePage();

  // Registers a handle for "/js-response" in the embedded web server that
  // responds with |script| as the response body, and then navigates to |path|.
  // |path| usually points to a page that will somehow make a request to
  // "/js-response".
  void NavigateToAndRespondWithScript(const std::string& path,
                                      const std::string& script);
};

void WakeLockBrowserTest::SetUpCommandLine(base::CommandLine* command_line) {
  command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
                                  "SystemWakeLock");
}

void WakeLockBrowserTest::NavigateToSimplePage() {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/simple.html")));
}

void WakeLockBrowserTest::NavigateToAndRespondWithScript(
    const std::string& path,
    const std::string& script) {
  base::RunLoop loop;
  embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
      &RespondWithJS, "/js-response", script, loop.QuitClosure()));
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(path)));
  loop.Run();
}

// https://w3c.github.io/screen-wake-lock/#the-request-method
// Screen locks are never allowed from workers.
IN_PROC_BROWSER_TEST_F(WakeLockBrowserTest, RequestScreenLockFromWorker) {
  permissions::PermissionRequestObserver observer(
      browser()->GetTabStripModel()->GetActiveWebContents());
  const std::string kWorkerScript =
      "navigator.wakeLock.request('screen').catch(err => "
      "    self.postMessage(err.name))";
  NavigateToAndRespondWithScript(
      "/workers/create_dedicated_worker.html?worker_url=/js-response",
      kWorkerScript);
  EXPECT_EQ(
      "NotAllowedError",
      content::EvalJs(browser()->GetTabStripModel()->GetActiveWebContents(),
                      "waitForMessage();"));
  EXPECT_FALSE(observer.request_shown());
}

// Requests for a system lock should always be denied, and there should be no
// permission prompt.
IN_PROC_BROWSER_TEST_F(WakeLockBrowserTest, RequestSystemLockFromWorker) {
  permissions::PermissionRequestObserver observer(
      browser()->GetTabStripModel()->GetActiveWebContents());
  const std::string kWorkerScript =
      "navigator.wakeLock.request('system').catch(err => "
      "    self.postMessage(err.name))";
  NavigateToAndRespondWithScript(
      "/workers/create_dedicated_worker.html?worker_url=/js-response",
      kWorkerScript);
  EXPECT_EQ(
      "NotAllowedError",
      content::EvalJs(browser()->GetTabStripModel()->GetActiveWebContents(),
                      "waitForMessage();"));
  EXPECT_FALSE(observer.request_shown());
}

IN_PROC_BROWSER_TEST_F(WakeLockBrowserTest, RequestPermissionScreen) {
  // Requests for a screen lock should always be granted, and there should be no
  // permission prompt.
  NavigateToSimplePage();

  permissions::PermissionRequestObserver observer(
      browser()->GetTabStripModel()->GetActiveWebContents());
  EXPECT_EQ(
      "granted",
      content::EvalJs(browser()->GetTabStripModel()->GetActiveWebContents(),
                      "navigator.wakeLock.request('screen').then(lock => {"
                      "    lock.release(); return 'granted'; });"));
  EXPECT_FALSE(observer.request_shown());
}

IN_PROC_BROWSER_TEST_F(WakeLockBrowserTest,
                       RequestPermissionScreenNoUserGesture) {
  // Requests for a screen lock should always be granted, and there should be no
  // permission prompt.
  NavigateToSimplePage();

  permissions::PermissionRequestObserver observer(
      browser()->GetTabStripModel()->GetActiveWebContents());
  EXPECT_EQ(
      "granted",
      content::EvalJs(browser()->GetTabStripModel()->GetActiveWebContents(),
                      "navigator.wakeLock.request('screen').then(lock => {"
                      "    lock.release(); return 'granted'; });",
                      content::EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE));
  EXPECT_FALSE(observer.request_shown());
}

IN_PROC_BROWSER_TEST_F(WakeLockBrowserTest, RequestPermissionSystem) {
  // Requests for a system lock should always be denied, and there should be no
  // permission prompt.
  NavigateToSimplePage();

  permissions::PermissionRequestObserver observer(
      browser()->GetTabStripModel()->GetActiveWebContents());
  EXPECT_EQ(
      "NotAllowedError",
      content::EvalJs(browser()->GetTabStripModel()->GetActiveWebContents(),
                      "navigator.wakeLock.request('system').catch(err => {"
                      "    return err.name; });"));
  EXPECT_FALSE(observer.request_shown());
}

IN_PROC_BROWSER_TEST_F(WakeLockBrowserTest,
                       RequestPermissionSystemNoUserGesture) {
  // Requests for a system lock should always be denied, and there should be no
  // permission prompt.
  NavigateToSimplePage();

  permissions::PermissionRequestObserver observer(
      browser()->GetTabStripModel()->GetActiveWebContents());
  EXPECT_EQ(
      "NotAllowedError",
      content::EvalJs(browser()->GetTabStripModel()->GetActiveWebContents(),
                      "navigator.wakeLock.request('system').catch(err => {"
                      "    return err.name; });",
                      content::EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE));
  EXPECT_FALSE(observer.request_shown());
}
