Commit 2c6ce192 authored by Reilly Grant's avatar Reilly Grant Committed by Commit Bot

Ensure device choosers are closed on navigation

The requestDevice() IPCs can race with navigation. This change ensures
that choosers are closed on navigation and adds browser tests to
exercise this for Web Bluetooth and WebUSB.

Bug: 723503
Change-Id: I66760161220e17bd2be9309cca228d161fe76e9c
Reviewed-on: https://chromium-review.googlesource.com/1099961
Commit-Queue: Reilly Grant <reillyg@chromium.org>
Reviewed-by: default avatarMichael Wasserman <msw@chromium.org>
Reviewed-by: default avatarJeffrey Yasskin <jyasskin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#569900}
parent 5be5597b
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include "base/logging.h" #include "base/logging.h"
#include "chrome/browser/ui/bluetooth/bluetooth_chooser_controller.h" #include "chrome/browser/ui/bluetooth/bluetooth_chooser_controller.h"
#include "components/bubble/bubble_controller.h"
BluetoothChooserDesktop::BluetoothChooserDesktop( BluetoothChooserDesktop::BluetoothChooserDesktop(
BluetoothChooserController* bluetooth_chooser_controller) BluetoothChooserController* bluetooth_chooser_controller)
...@@ -18,6 +19,8 @@ BluetoothChooserDesktop::~BluetoothChooserDesktop() { ...@@ -18,6 +19,8 @@ BluetoothChooserDesktop::~BluetoothChooserDesktop() {
// that the EventHandler can be destroyed any time after the BluetoothChooser // that the EventHandler can be destroyed any time after the BluetoothChooser
// instance. // instance.
bluetooth_chooser_controller_->ResetEventHandler(); bluetooth_chooser_controller_->ResetEventHandler();
if (bubble_)
bubble_->CloseBubble(BUBBLE_CLOSE_FORCED);
} }
void BluetoothChooserDesktop::SetAdapterPresence(AdapterPresence presence) { void BluetoothChooserDesktop::SetAdapterPresence(AdapterPresence presence) {
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#define CHROME_BROWSER_UI_BLUETOOTH_BLUETOOTH_CHOOSER_DESKTOP_H_ #define CHROME_BROWSER_UI_BLUETOOTH_BLUETOOTH_CHOOSER_DESKTOP_H_
#include "base/macros.h" #include "base/macros.h"
#include "components/bubble/bubble_reference.h"
#include "content/public/browser/bluetooth_chooser.h" #include "content/public/browser/bluetooth_chooser.h"
class BluetoothChooserController; class BluetoothChooserController;
...@@ -29,10 +30,18 @@ class BluetoothChooserDesktop : public content::BluetoothChooser { ...@@ -29,10 +30,18 @@ class BluetoothChooserDesktop : public content::BluetoothChooser {
bool is_paired, bool is_paired,
int signal_strength_level) override; int signal_strength_level) override;
// Sets a reference to the bubble being displayed so that it can be closed if
// this object is destroyed.
void set_bubble(base::WeakPtr<BubbleController> bubble) {
bubble_ = std::move(bubble);
}
private: private:
// Weak. DeviceChooserContentView[Cocoa] owns it. // Weak. DeviceChooserContentView[Cocoa] owns it.
BluetoothChooserController* bluetooth_chooser_controller_; BluetoothChooserController* bluetooth_chooser_controller_;
base::WeakPtr<BubbleController> bubble_;
DISALLOW_COPY_AND_ASSIGN(BluetoothChooserDesktop); DISALLOW_COPY_AND_ASSIGN(BluetoothChooserDesktop);
}; };
......
...@@ -1350,6 +1350,7 @@ std::unique_ptr<content::BluetoothChooser> Browser::RunBluetoothChooser( ...@@ -1350,6 +1350,7 @@ std::unique_ptr<content::BluetoothChooser> Browser::RunBluetoothChooser(
WebContents::FromRenderFrameHost(frame)); WebContents::FromRenderFrameHost(frame));
BubbleReference bubble_reference = browser->GetBubbleManager()->ShowBubble( BubbleReference bubble_reference = browser->GetBubbleManager()->ShowBubble(
std::move(chooser_bubble_delegate)); std::move(chooser_bubble_delegate));
bluetooth_chooser_desktop->set_bubble(std::move(bubble_reference));
return std::move(bluetooth_chooser_desktop); return std::move(bluetooth_chooser_desktop);
} }
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_frame_host.h"
#include "content/public/common/content_switches.h" #include "content/public/common/content_switches.h"
#include "content/public/test/browser_test_utils.h" #include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "device/base/mock_device_client.h" #include "device/base/mock_device_client.h"
#include "device/usb/mock_usb_device.h" #include "device/usb/mock_usb_device.h"
#include "device/usb/mock_usb_service.h" #include "device/usb/mock_usb_service.h"
...@@ -87,12 +88,21 @@ class TestContentBrowserClient : public ChromeContentBrowserClient { ...@@ -87,12 +88,21 @@ class TestContentBrowserClient : public ChromeContentBrowserClient {
void CreateUsbChooserService( void CreateUsbChooserService(
content::RenderFrameHost* render_frame_host, content::RenderFrameHost* render_frame_host,
device::mojom::UsbChooserServiceRequest request) override { device::mojom::UsbChooserServiceRequest request) override {
mojo::MakeStrongBinding( if (use_real_chooser_) {
std::make_unique<FakeChooserService>(render_frame_host), ChromeContentBrowserClient::CreateUsbChooserService(render_frame_host,
std::move(request)); std::move(request));
} else {
mojo::MakeStrongBinding(
std::make_unique<FakeChooserService>(render_frame_host),
std::move(request));
}
} }
void UseRealChooser() { use_real_chooser_ = true; }
private: private:
bool use_real_chooser_ = false;
DISALLOW_COPY_AND_ASSIGN(TestContentBrowserClient); DISALLOW_COPY_AND_ASSIGN(TestContentBrowserClient);
}; };
...@@ -140,6 +150,8 @@ class WebUsbTest : public InProcessBrowserTest { ...@@ -140,6 +150,8 @@ class WebUsbTest : public InProcessBrowserTest {
const GURL& origin() { return origin_; } const GURL& origin() { return origin_; }
void UseRealChooser() { test_content_browser_client_.UseRealChooser(); }
private: private:
std::unique_ptr<MockDeviceClient> device_client_; std::unique_ptr<MockDeviceClient> device_client_;
scoped_refptr<MockUsbDevice> mock_device_; scoped_refptr<MockUsbDevice> mock_device_;
...@@ -287,4 +299,22 @@ IN_PROC_BROWSER_TEST_F(WebUsbTest, AddRemoveDeviceEphemeral) { ...@@ -287,4 +299,22 @@ IN_PROC_BROWSER_TEST_F(WebUsbTest, AddRemoveDeviceEphemeral) {
EXPECT_EQ("", result); EXPECT_EQ("", result);
} }
IN_PROC_BROWSER_TEST_F(WebUsbTest, NavigateWithChooserCrossOrigin) {
UseRealChooser();
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver observer(
web_contents, 1 /* number_of_navigations */,
content::MessageLoopRunner::QuitMode::DEFERRED);
EXPECT_TRUE(content::ExecuteScript(
web_contents,
"navigator.usb.requestDevice({ filters: [] });"
"document.location.href = \"https://google.com\";"));
observer.Wait();
EXPECT_EQ(0u, browser()->GetBubbleManager()->GetBubbleCountForTesting());
}
} // namespace } // namespace
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/usb/web_usb_permission_provider.h" #include "chrome/browser/usb/web_usb_permission_provider.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_frame_host.h"
#include "content/public/common/content_features.h" #include "content/public/common/content_features.h"
#include "device/usb/mojo/device_manager_impl.h" #include "device/usb/mojo/device_manager_impl.h"
...@@ -113,6 +114,13 @@ void UsbTabHelper::RenderFrameDeleted(RenderFrameHost* render_frame_host) { ...@@ -113,6 +114,13 @@ void UsbTabHelper::RenderFrameDeleted(RenderFrameHost* render_frame_host) {
NotifyTabStateChanged(); NotifyTabStateChanged();
} }
void UsbTabHelper::DidFinishNavigation(content::NavigationHandle* handle) {
if (handle->HasCommitted() && !handle->IsSameDocument()) {
frame_usb_services_.erase(handle->GetRenderFrameHost());
NotifyTabStateChanged();
}
}
FrameUsbServices* UsbTabHelper::GetFrameUsbService( FrameUsbServices* UsbTabHelper::GetFrameUsbService(
content::RenderFrameHost* render_frame_host) { content::RenderFrameHost* render_frame_host) {
FrameUsbServicesMap::const_iterator it = FrameUsbServicesMap::const_iterator it =
......
...@@ -55,6 +55,7 @@ class UsbTabHelper : public content::WebContentsObserver, ...@@ -55,6 +55,7 @@ class UsbTabHelper : public content::WebContentsObserver,
// content::WebContentsObserver overrides: // content::WebContentsObserver overrides:
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override; void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
void DidFinishNavigation(content::NavigationHandle* handle) override;
FrameUsbServices* GetFrameUsbService( FrameUsbServices* GetFrameUsbService(
content::RenderFrameHost* render_frame_host); content::RenderFrameHost* render_frame_host);
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "content/public/browser/render_process_host.h" #include "content/public/browser/render_process_host.h"
#include "content/public/common/content_switches.h" #include "content/public/common/content_switches.h"
#include "content/public/test/browser_test_utils.h" #include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h" #include "device/bluetooth/test/mock_bluetooth_adapter.h"
...@@ -178,4 +179,31 @@ IN_PROC_BROWSER_TEST_F(WebBluetoothTest, BlocklistShouldBlock) { ...@@ -178,4 +179,31 @@ IN_PROC_BROWSER_TEST_F(WebBluetoothTest, BlocklistShouldBlock) {
testing::MatchesRegex("SecurityError: .*blocklisted UUID.*")); testing::MatchesRegex("SecurityError: .*blocklisted UUID.*"));
} }
IN_PROC_BROWSER_TEST_F(WebBluetoothTest, NavigateWithChooserCrossOrigin) {
// Fake the BluetoothAdapter to say it's present.
scoped_refptr<device::MockBluetoothAdapter> adapter =
new testing::NiceMock<device::MockBluetoothAdapter>;
EXPECT_CALL(*adapter, IsPresent()).WillRepeatedly(Return(true));
auto bt_global_values =
device::BluetoothAdapterFactory::Get().InitGlobalValuesForTesting();
bt_global_values->SetLESupported(true);
device::BluetoothAdapterFactory::SetAdapterForTesting(adapter);
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver observer(
web_contents, 1 /* number_of_navigations */,
content::MessageLoopRunner::QuitMode::DEFERRED);
EXPECT_TRUE(content::ExecuteScript(
web_contents,
"navigator.bluetooth.requestDevice({filters: [{name: 'Hello'}]});"
"document.location.href = \"https://google.com\";"));
observer.Wait();
EXPECT_EQ(0u, browser()->GetBubbleManager()->GetBubbleCountForTesting());
EXPECT_EQ(GURL("https://google.com"), web_contents->GetLastCommittedURL());
}
} // namespace } // namespace
...@@ -81,6 +81,10 @@ void BubbleManager::RemoveBubbleManagerObserver( ...@@ -81,6 +81,10 @@ void BubbleManager::RemoveBubbleManagerObserver(
observers_.RemoveObserver(observer); observers_.RemoveObserver(observer);
} }
size_t BubbleManager::GetBubbleCountForTesting() const {
return controllers_.size();
}
void BubbleManager::FinalizePendingRequests() { void BubbleManager::FinalizePendingRequests() {
// Return if already "Finalized". // Return if already "Finalized".
if (manager_state_ == NO_MORE_BUBBLES) if (manager_state_ == NO_MORE_BUBBLES)
......
...@@ -70,6 +70,9 @@ class BubbleManager { ...@@ -70,6 +70,9 @@ class BubbleManager {
// Remove an observer from this BubbleManager. // Remove an observer from this BubbleManager.
void RemoveBubbleManagerObserver(BubbleManagerObserver* observer); void RemoveBubbleManagerObserver(BubbleManagerObserver* observer);
// Returns the number of bubbles currently being managed.
size_t GetBubbleCountForTesting() const;
protected: protected:
// Will close any open bubbles and prevent new ones from being shown. // Will close any open bubbles and prevent new ones from being shown.
void FinalizePendingRequests(); void FinalizePendingRequests();
......
...@@ -1242,6 +1242,10 @@ BluetoothAllowedDevices& WebBluetoothServiceImpl::allowed_devices() { ...@@ -1242,6 +1242,10 @@ BluetoothAllowedDevices& WebBluetoothServiceImpl::allowed_devices() {
} }
void WebBluetoothServiceImpl::ClearState() { void WebBluetoothServiceImpl::ClearState() {
// Releasing the adapter will drop references to callbacks that have not yet
// been executed. The binding must be closed first so that this is allowed.
binding_.Close();
characteristic_id_to_notify_session_.clear(); characteristic_id_to_notify_session_.clear();
pending_primary_services_requests_.clear(); pending_primary_services_requests_.clear();
descriptor_id_to_characteristic_id_.clear(); descriptor_id_to_characteristic_id_.clear();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment