Bluetooth Scanning API: Revoke granted permission when tab is switched to background

This CL implements revoking previously granted permission for Bluetooth Scanning API
when a tab is switched to background.

Design doc:
https://docs.google.com/document/d/1k4WM56Dx7sCu2k2MjvOdXArNz2Dy8BA1vt2VYHA6v7A/edit

Bug: 953075
Change-Id: I6690dcab7bfe8b41162cb3d1c98f8c18863955ba
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1614423
Commit-Queue: Jun Cai <juncai@chromium.org>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#666157}
diff --git a/content/browser/bluetooth/web_bluetooth_service_impl.cc b/content/browser/bluetooth/web_bluetooth_service_impl.cc
index 41d2063..ef01e844 100644
--- a/content/browser/bluetooth/web_bluetooth_service_impl.cc
+++ b/content/browser/bluetooth/web_bluetooth_service_impl.cc
@@ -410,6 +410,15 @@
   }
 }
 
+void WebBluetoothServiceImpl::OnVisibilityChanged(Visibility visibility) {
+  if (visibility == content::Visibility::HIDDEN ||
+      visibility == content::Visibility::OCCLUDED) {
+    allowed_scan_filters_.clear();
+    accept_all_advertisements_ = false;
+    scanning_clients_.clear();
+  }
+}
+
 void WebBluetoothServiceImpl::AdapterPoweredChanged(
     device::BluetoothAdapter* adapter,
     bool powered) {
diff --git a/content/browser/bluetooth/web_bluetooth_service_impl.h b/content/browser/bluetooth/web_bluetooth_service_impl.h
index 890b0cc..356c24f9 100644
--- a/content/browser/bluetooth/web_bluetooth_service_impl.h
+++ b/content/browser/bluetooth/web_bluetooth_service_impl.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/optional.h"
 #include "content/browser/bad_message.h"
@@ -81,7 +82,12 @@
       BluetoothDeviceScanningPromptController* prompt_controller);
 
  private:
+  FRIEND_TEST_ALL_PREFIXES(WebBluetoothServiceImplTest,
+                           BluetoothScanningPermissionRevokedWhenTabHidden);
+  FRIEND_TEST_ALL_PREFIXES(WebBluetoothServiceImplTest,
+                           BluetoothScanningPermissionRevokedWhenTabOccluded);
   friend class FrameConnectedBluetoothDevicesTest;
+  friend class WebBluetoothServiceImplTest;
   using PrimaryServicesRequestCallback =
       base::OnceCallback<void(device::BluetoothDevice*)>;
   using ScanFilters = std::vector<blink::mojom::WebBluetoothLeScanFilterPtr>;
@@ -133,6 +139,7 @@
   // These functions should always check that the affected RenderFrameHost
   // is this->render_frame_host_ and not some other frame in the same tab.
   void DidFinishNavigation(NavigationHandle* navigation_handle) override;
+  void OnVisibilityChanged(Visibility visibility) override;
 
   // BluetoothAdapter::Observer:
   void AdapterPoweredChanged(device::BluetoothAdapter* adapter,
diff --git a/content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc b/content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc
new file mode 100644
index 0000000..d633a748
--- /dev/null
+++ b/content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc
@@ -0,0 +1,150 @@
+// Copyright 2019 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 "content/browser/bluetooth/web_bluetooth_service_impl.h"
+
+#include "base/macros.h"
+#include "base/test/bind_test_util.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/test/test_render_view_host.h"
+#include "content/test/test_web_contents.h"
+#include "device/bluetooth/bluetooth_adapter_factory_wrapper.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Return;
+
+namespace content {
+
+namespace {
+
+const char kBatteryServiceUUIDString[] = "0000180f-0000-1000-8000-00805f9b34fb";
+
+class FakeBluetoothScanningPrompt : public BluetoothScanningPrompt {
+ public:
+  explicit FakeBluetoothScanningPrompt(const EventHandler& event_handler) {
+    event_handler.Run(content::BluetoothScanningPrompt::Event::kAllow);
+  }
+  ~FakeBluetoothScanningPrompt() override = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FakeBluetoothScanningPrompt);
+};
+
+class FakeBluetoothAdapter : public device::MockBluetoothAdapter {
+ public:
+  FakeBluetoothAdapter() = default;
+
+  // device::BluetoothAdapter:
+  void StartScanWithFilter(
+      std::unique_ptr<device::BluetoothDiscoveryFilter> discovery_filter,
+      DiscoverySessionResultCallback callback) override {
+    std::move(callback).Run(
+        /*is_error=*/false,
+        device::UMABluetoothDiscoverySessionOutcome::SUCCESS);
+  }
+
+ protected:
+  ~FakeBluetoothAdapter() override = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FakeBluetoothAdapter);
+};
+
+class FakeWebContentsDelegate : public content::WebContentsDelegate {
+ public:
+  FakeWebContentsDelegate() = default;
+  ~FakeWebContentsDelegate() override = default;
+
+  std::unique_ptr<BluetoothScanningPrompt> ShowBluetoothScanningPrompt(
+      RenderFrameHost* frame,
+      const BluetoothScanningPrompt::EventHandler& event_handler) override {
+    return std::make_unique<FakeBluetoothScanningPrompt>(event_handler);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FakeWebContentsDelegate);
+};
+
+}  // namespace
+
+class WebBluetoothServiceImplTest : public RenderViewHostImplTestHarness {
+ public:
+  WebBluetoothServiceImplTest() = default;
+  ~WebBluetoothServiceImplTest() override = default;
+
+  void SetUp() override {
+    RenderViewHostImplTestHarness::SetUp();
+
+    // Set up an adapter.
+    scoped_refptr<FakeBluetoothAdapter> adapter(new FakeBluetoothAdapter());
+    EXPECT_CALL(*adapter, IsPresent()).WillRepeatedly(Return(true));
+    device::BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterForTesting(
+        adapter);
+
+    contents()->GetMainFrame()->InitializeRenderFrameIfNeeded();
+    contents()->SetDelegate(&delegate_);
+
+    // Simulate a frame connected to a bluetooth service.
+    service_ =
+        contents()->GetMainFrame()->CreateWebBluetoothServiceForTesting();
+
+    blink::mojom::WebBluetoothScanClientAssociatedPtrInfo client_info;
+    mojo::MakeRequest(&client_info);
+    auto options = blink::mojom::WebBluetoothRequestLEScanOptions::New();
+    base::Optional<std::vector<device::BluetoothUUID>> services;
+    services.emplace();
+    services->push_back(device::BluetoothUUID(kBatteryServiceUUIDString));
+    auto filter =
+        blink::mojom::WebBluetoothLeScanFilter::New(services, "a", "b");
+    options->filters.emplace();
+    options->filters->push_back(filter.Clone());
+    filters_.emplace();
+    filters_->push_back(filter.Clone());
+    service_->RequestScanningStart(
+        std::move(client_info), std::move(options),
+        base::BindLambdaForTesting(
+            [&](blink::mojom::RequestScanningStartResultPtr p) {
+              loop_.Quit();
+            }));
+    loop_.Run();
+  }
+
+  void TearDown() override {
+    service_ = nullptr;
+    RenderViewHostImplTestHarness::TearDown();
+  }
+
+ protected:
+  WebBluetoothServiceImpl* service_;
+  base::Optional<WebBluetoothServiceImpl::ScanFilters> filters_;
+  FakeWebContentsDelegate delegate_;
+  base::RunLoop loop_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(WebBluetoothServiceImplTest);
+};
+
+TEST_F(WebBluetoothServiceImplTest,
+       BluetoothScanningPermissionRevokedWhenTabHidden) {
+  EXPECT_TRUE(service_->AreScanFiltersAllowed(filters_));
+
+  contents()->SetVisibility(content::Visibility::HIDDEN);
+
+  // The previously granted Bluetooth scanning permission should be revoked.
+  EXPECT_FALSE(service_->AreScanFiltersAllowed(filters_));
+}
+
+TEST_F(WebBluetoothServiceImplTest,
+       BluetoothScanningPermissionRevokedWhenTabOccluded) {
+  EXPECT_TRUE(service_->AreScanFiltersAllowed(filters_));
+
+  contents()->SetVisibility(content::Visibility::OCCLUDED);
+
+  // The previously granted Bluetooth scanning permission should be revoked.
+  EXPECT_FALSE(service_->AreScanFiltersAllowed(filters_));
+}
+
+}  // namespace content
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index f14ca1b..318394f4 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1441,6 +1441,7 @@
     "../browser/bluetooth/bluetooth_device_chooser_controller_unittest.cc",
     "../browser/bluetooth/bluetooth_util_unittest.cc",
     "../browser/bluetooth/frame_connected_bluetooth_devices_unittest.cc",
+    "../browser/bluetooth/web_bluetooth_service_impl_unittest.cc",
     "../browser/browser_associated_interface_unittest.cc",
     "../browser/browser_main_loop_unittest.cc",
     "../browser/browser_thread_unittest.cc",