910e62b5创建于 1月15日历史提交
// 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 <utility>

#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "content/browser/hid/hid_test_utils.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/hid_chooser.h"
#include "content/public/browser/hid_delegate.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_content_browser_client.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/shell/browser/shell.h"
#include "services/device/public/cpp/test/fake_hid_manager.h"
#include "services/device/public/mojom/hid.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/hid/hid.mojom.h"

using testing::ByMove;
using testing::Exactly;
using testing::Return;

namespace content {

namespace {

// Create a device with a single collection containing an input report and an
// output report. Both reports have report ID 0.
device::mojom::HidDeviceInfoPtr CreateTestDeviceWithInputAndOutputReports() {
  auto collection = device::mojom::HidCollectionInfo::New();
  collection->usage = device::mojom::HidUsageAndPage::New(0x0001, 0xff00);
  collection->input_reports.push_back(
      device::mojom::HidReportDescription::New());
  collection->output_reports.push_back(
      device::mojom::HidReportDescription::New());

  auto device = device::mojom::HidDeviceInfo::New();
  device->guid = "test-guid";
  device->collections.push_back(std::move(collection));
  return device;
}

class HidBrowserTestContentBrowserClient
    : public ContentBrowserTestContentBrowserClient {
 public:
  MockHidDelegate& delegate() { return delegate_; }

  // ContentBrowserClient:
  HidDelegate* GetHidDelegate() override { return &delegate_; }

 private:
  MockHidDelegate delegate_;
};

class HidTest : public ContentBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(
        switches::kEnableExperimentalWebPlatformFeatures);
  }

  void SetUpOnMainThread() override {
    test_client_ = std::make_unique<HidBrowserTestContentBrowserClient>();
    ON_CALL(delegate(), GetHidManager).WillByDefault(Return(&hid_manager_));
  }

  void TearDownOnMainThread() override { test_client_.reset(); }

  MockHidDelegate& delegate() { return test_client_->delegate(); }
  device::FakeHidManager* hid_manager() { return &hid_manager_; }

 private:
  std::unique_ptr<HidBrowserTestContentBrowserClient> test_client_;
  device::FakeHidManager hid_manager_;
};

}  // namespace

IN_PROC_BROWSER_TEST_F(HidTest, GetDevices) {
  EXPECT_TRUE(NavigateToURL(shell(), GetTestUrl(nullptr, "simple_page.html")));

  // Three devices are added but only two will have permission granted.
  for (int i = 0; i < 3; i++) {
    auto device = CreateTestDeviceWithInputAndOutputReports();
    device->guid = base::StringPrintf("test-guid-%02d", i);
    hid_manager()->AddDevice(std::move(device));
  }

  EXPECT_CALL(delegate(), HasDevicePermission)
      .WillOnce(Return(true))
      .WillOnce(Return(false))
      .WillOnce(Return(true));

  EXPECT_EQ(
      2,
      EvalJs(shell(),
             R"(navigator.hid.getDevices().then(devices => devices.length))"));
}

IN_PROC_BROWSER_TEST_F(HidTest, RequestDevice) {
  EXPECT_TRUE(NavigateToURL(shell(), GetTestUrl(nullptr, "simple_page.html")));

  EXPECT_CALL(delegate(), CanRequestDevicePermission).WillOnce(Return(true));

  std::vector<device::mojom::HidDeviceInfoPtr> devices;
  devices.push_back(CreateTestDeviceWithInputAndOutputReports());
  EXPECT_CALL(delegate(), RunChooserInternal)
      .WillOnce(Return(ByMove(std::move(devices))));

  EXPECT_EQ(true, EvalJs(shell(),
                         R"((async () => {
               let devices = await navigator.hid.requestDevice({filters:[]});
               return devices instanceof Array
                      && devices.length == 1
                      && devices[0] instanceof HIDDevice;
             })())"));
}

IN_PROC_BROWSER_TEST_F(HidTest, DisallowRequestDevice) {
  EXPECT_TRUE(NavigateToURL(shell(), GetTestUrl(nullptr, "simple_page.html")));

  EXPECT_CALL(delegate(), CanRequestDevicePermission).WillOnce(Return(false));
  EXPECT_CALL(delegate(), RunChooserInternal).Times(Exactly(0));

  EXPECT_EQ(0, EvalJs(shell(),
                      R"((async () => {
               let devices = await navigator.hid.requestDevice({filters:[]});
               return devices.length;
             })())"));
}

IN_PROC_BROWSER_TEST_F(HidTest, ProtectedReportsAreFiltered) {
  LOG(ERROR) << "HidTest.ProtectedReportsAreFiltered";
  EXPECT_TRUE(NavigateToURL(shell(), GetTestUrl(nullptr, "simple_page.html")));

  auto device = CreateTestDeviceWithInputAndOutputReports();

  // Mark the input report as protected.
  device->protected_input_report_ids = std::vector<uint8_t>{0};

  hid_manager()->AddDevice(std::move(device));

  EXPECT_CALL(delegate(), HasDevicePermission).WillOnce(Return(true));

  EXPECT_EQ(true, EvalJs(shell(),
                         R"((async () => {
             let devices = await navigator.hid.getDevices();
             return devices instanceof Array
                    && devices.length == 1
                    && devices[0] instanceof HIDDevice
                    && devices[0].collections instanceof Array
                    && devices[0].collections.length == 1
                    && devices[0].collections[0].inputReports instanceof Array
                    && devices[0].collections[0].inputReports.length == 0
                    && devices[0].collections[0].outputReports instanceof Array
                    && devices[0].collections[0].outputReports.length == 1;
           })())"));
}

IN_PROC_BROWSER_TEST_F(HidTest, DeviceWithAllProtectedReportsIsExcluded) {
  EXPECT_TRUE(NavigateToURL(shell(), GetTestUrl(nullptr, "simple_page.html")));

  auto device = CreateTestDeviceWithInputAndOutputReports();

  // Mark both the input and output reports as protected.
  device->protected_input_report_ids = std::vector<uint8_t>{0};
  device->protected_output_report_ids = std::vector<uint8_t>{0};

  hid_manager()->AddDevice(std::move(device));

  EXPECT_EQ(true, EvalJs(shell(),
                         R"((async () => {
               let devices = await navigator.hid.getDevices();
               return devices instanceof Array && devices.length == 0;
             })())"));
}

class HidFencedFramesBrowserTest : public HidTest {
 public:
  HidFencedFramesBrowserTest() = default;
  HidFencedFramesBrowserTest(const HidFencedFramesBrowserTest&) = delete;
  HidFencedFramesBrowserTest& operator=(const HidFencedFramesBrowserTest&) =
      delete;
  ~HidFencedFramesBrowserTest() override = default;

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

    ASSERT_TRUE(embedded_test_server()->Start());
  }

  content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
    return fenced_frame_helper_;
  }

  WebContents* GetWebContents() { return shell()->web_contents(); }

 private:
  content::test::FencedFrameTestHelper fenced_frame_helper_;
};

IN_PROC_BROWSER_TEST_F(HidFencedFramesBrowserTest, BlockFromFencedFrame) {
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("/simple_page.html")));

  // Three devices are added but only two will have permission granted.
  for (int i = 0; i < 3; i++) {
    auto device = CreateTestDeviceWithInputAndOutputReports();
    device->guid = base::StringPrintf("test-guid-%02d", i);
    hid_manager()->AddDevice(std::move(device));
  }

  EXPECT_CALL(delegate(), HasDevicePermission)
      .WillOnce(Return(true))
      .WillOnce(Return(false))
      .WillOnce(Return(true));

  EXPECT_EQ(
      2,
      EvalJs(shell(),
             R"(navigator.hid.getDevices().then(devices => devices.length))"));

  // Loads a fenced frame
  const GURL kFencedFrameUrl =
      embedded_test_server()->GetURL("/fenced_frames/empty.html");
  content::RenderFrameHost* render_frame_host =
      fenced_frame_test_helper().CreateFencedFrame(
          GetWebContents()->GetPrimaryMainFrame(), kFencedFrameUrl);
  ASSERT_NE(nullptr, render_frame_host);

  // Tries to request a device from the fenced frame, which must cause an error.
  constexpr char kFencedFrameError[] =
      "Access to the feature \"hid\" is disallowed by permissions policy.";
  auto result = content::EvalJs(
      render_frame_host,
      R"(navigator.hid.getDevices().then(devices => devices.length))");
  EXPECT_THAT(result,
              EvalJsResult::ErrorIs(::testing::HasSubstr(kFencedFrameError)));
}

}  // namespace content