0

Add browser tests for USB device add/remove events.

These tests are essentially copies of the HID device add and remove
event tests. In order to reconcile issues with the MockUsbService
lifetime it is now tracked by a MessageLoop::DestructionObserver at the
generic UsbService level. This is similar to the pattern used by the
HidService until it was moved to the UI thread and could use a
LazyInstance.

BUG=411715

Review URL: https://codereview.chromium.org/800963005

Cr-Commit-Position: refs/heads/master@{#312538}
This commit is contained in:
reillyg
2015-01-21 18:39:59 -08:00
committed by Commit bot
parent 8acd5be1ab
commit 45919813ba
13 changed files with 353 additions and 187 deletions

@ -123,20 +123,18 @@ class DevicePermissionsManagerTest : public testing::Test {
protected:
void SetUp() override {
testing::Test::SetUp();
env_.reset(new TestExtensionEnvironment());
env_->GetExtensionPrefs(); // Force creation before adding extensions.
extension_ =
env_->MakeExtension(*base::test::ParseJson(
"{"
" \"app\": {"
" \"background\": {"
" \"scripts\": [\"background.js\"]"
" }"
" },"
" \"permissions\": ["
" \"usb\""
" ]"
"}"));
env_.GetExtensionPrefs(); // Force creation before adding extensions.
extension_ = env_.MakeExtension(*base::test::ParseJson(
"{"
" \"app\": {"
" \"background\": {"
" \"scripts\": [\"background.js\"]"
" }"
" },"
" \"permissions\": ["
" \"usb\""
" ]"
"}"));
device0_ = new MockUsbDevice("ABCDE");
device1_ = new MockUsbDevice("");
device2_ = new MockUsbDevice("12345");
@ -145,12 +143,7 @@ class DevicePermissionsManagerTest : public testing::Test {
UsbService::SetInstanceForTest(usb_service_);
}
void TearDown() override {
env_.reset(nullptr);
UsbService::SetInstanceForTest(nullptr);
}
scoped_ptr<extensions::TestExtensionEnvironment> env_;
extensions::TestExtensionEnvironment env_;
const extensions::Extension* extension_;
MockUsbService* usb_service_;
scoped_refptr<MockUsbDevice> device0_;
@ -161,7 +154,7 @@ class DevicePermissionsManagerTest : public testing::Test {
TEST_F(DevicePermissionsManagerTest, AllowAndClearDevices) {
DevicePermissionsManager* manager =
DevicePermissionsManager::Get(env_->profile());
DevicePermissionsManager::Get(env_.profile());
AllowUsbDevice(manager, extension_, device0_);
AllowUsbDevice(manager, extension_, device1_);
@ -205,7 +198,7 @@ TEST_F(DevicePermissionsManagerTest, AllowAndClearDevices) {
TEST_F(DevicePermissionsManagerTest, SuspendExtension) {
DevicePermissionsManager* manager =
DevicePermissionsManager::Get(env_->profile());
DevicePermissionsManager::Get(env_.profile());
AllowUsbDevice(manager, extension_, device0_);
AllowUsbDevice(manager, extension_, device1_);
@ -232,7 +225,7 @@ TEST_F(DevicePermissionsManagerTest, SuspendExtension) {
TEST_F(DevicePermissionsManagerTest, DisconnectDevice) {
DevicePermissionsManager* manager =
DevicePermissionsManager::Get(env_->profile());
DevicePermissionsManager::Get(env_.profile());
AllowUsbDevice(manager, extension_, device0_);
AllowUsbDevice(manager, extension_, device1_);
@ -260,7 +253,7 @@ TEST_F(DevicePermissionsManagerTest, DisconnectDevice) {
TEST_F(DevicePermissionsManagerTest, RevokeAndRegrantAccess) {
DevicePermissionsManager* manager =
DevicePermissionsManager::Get(env_->profile());
DevicePermissionsManager::Get(env_.profile());
AllowUsbDevice(manager, extension_, device0_);
AllowUsbDevice(manager, extension_, device1_);
@ -296,7 +289,7 @@ TEST_F(DevicePermissionsManagerTest, RevokeAndRegrantAccess) {
TEST_F(DevicePermissionsManagerTest, UpdateLastUsed) {
DevicePermissionsManager* manager =
DevicePermissionsManager::Get(env_->profile());
DevicePermissionsManager::Get(env_.profile());
AllowUsbDevice(manager, extension_, device0_);
scoped_ptr<DevicePermissions> device_permissions =
@ -321,11 +314,11 @@ TEST_F(DevicePermissionsManagerTest, LoadPrefs) {
" \"vendor_id\": 0"
" }"
"]");
env_->GetExtensionPrefs()->UpdateExtensionPref(extension_->id(), "devices",
prefs_value.release());
env_.GetExtensionPrefs()->UpdateExtensionPref(extension_->id(), "devices",
prefs_value.release());
DevicePermissionsManager* manager =
DevicePermissionsManager::Get(env_->profile());
DevicePermissionsManager::Get(env_.profile());
scoped_ptr<DevicePermissions> device_permissions =
manager->GetForExtension(extension_->id());
ASSERT_TRUE(FindEntry(device_permissions.get(), device0_).get());

@ -25,8 +25,10 @@ source_set("usb") {
"usb_error.h",
"usb_ids.cc",
"usb_ids.h",
"usb_service.cc",
"usb_service.h",
"usb_service_impl.cc",
"usb_service_impl.h",
generated_ids,
]

@ -34,8 +34,10 @@
'usb_error.h',
'usb_ids.cc',
'usb_ids.h',
'usb_service.cc',
'usb_service.h',
'usb_service_impl.cc',
'usb_service_impl.h',
],
'actions': [
{

90
device/usb/usb_service.cc Normal file

@ -0,0 +1,90 @@
// Copyright 2015 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 "device/usb/usb_service.h"
#include "base/message_loop/message_loop.h"
#include "device/usb/usb_device.h"
#include "device/usb/usb_service_impl.h"
namespace device {
namespace {
UsbService* g_service;
} // namespace
// This class manages the lifetime of the global UsbService instance so that
// it is destroyed when the current message loop is destroyed. A lazy instance
// cannot be used because this object does not live on the main thread.
class UsbService::Destroyer : private base::MessageLoop::DestructionObserver {
public:
explicit Destroyer(UsbService* usb_service) : usb_service_(usb_service) {
base::MessageLoop::current()->AddDestructionObserver(this);
}
~Destroyer() override {}
private:
// base::MessageLoop::DestructionObserver implementation.
void WillDestroyCurrentMessageLoop() override {
base::MessageLoop::current()->RemoveDestructionObserver(this);
delete usb_service_;
delete this;
g_service = nullptr;
}
UsbService* usb_service_;
};
void UsbService::Observer::OnDeviceAdded(scoped_refptr<UsbDevice> device) {
}
void UsbService::Observer::OnDeviceRemoved(scoped_refptr<UsbDevice> device) {
}
// static
UsbService* UsbService::GetInstance(
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
if (!g_service) {
g_service = UsbServiceImpl::Create(ui_task_runner);
// This object will clean itself up when the message loop is destroyed.
new Destroyer(g_service);
}
return g_service;
}
// static
void UsbService::SetInstanceForTest(UsbService* instance) {
g_service = instance;
new Destroyer(instance);
}
UsbService::UsbService() {
}
UsbService::~UsbService() {
}
void UsbService::AddObserver(Observer* observer) {
DCHECK(CalledOnValidThread());
observer_list_.AddObserver(observer);
}
void UsbService::RemoveObserver(Observer* observer) {
DCHECK(CalledOnValidThread());
observer_list_.RemoveObserver(observer);
}
void UsbService::NotifyDeviceAdded(scoped_refptr<UsbDevice> device) {
DCHECK(CalledOnValidThread());
FOR_EACH_OBSERVER(Observer, observer_list_, OnDeviceAdded(device));
}
void UsbService::NotifyDeviceRemoved(scoped_refptr<UsbDevice> device) {
DCHECK(CalledOnValidThread());
FOR_EACH_OBSERVER(Observer, observer_list_, OnDeviceRemoved(device));
}
} // namespace device

@ -55,8 +55,6 @@ class UsbService : public base::NonThreadSafe {
void RemoveObserver(Observer* observer);
protected:
friend struct base::DefaultDeleter<UsbService>;
UsbService();
virtual ~UsbService();
@ -65,6 +63,9 @@ class UsbService : public base::NonThreadSafe {
ObserverList<Observer, true> observer_list_;
private:
class Destroyer;
DISALLOW_COPY_AND_ASSIGN(UsbService);
};

@ -2,22 +2,17 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "device/usb/usb_service.h"
#include "device/usb/usb_service_impl.h"
#include <map>
#include <set>
#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/thread_task_runner_handle.h"
#include "device/usb/usb_context.h"
#include "device/usb/usb_device_impl.h"
#include "device/usb/usb_error.h"
#include "third_party/libusb/src/libusb/libusb.h"
#if defined(OS_WIN)
#include <usbiodef.h>
@ -28,79 +23,6 @@
namespace device {
namespace {
base::LazyInstance<scoped_ptr<UsbService> >::Leaky g_usb_service_instance =
LAZY_INSTANCE_INITIALIZER;
} // namespace
typedef struct libusb_device* PlatformUsbDevice;
typedef struct libusb_context* PlatformUsbContext;
class UsbServiceImpl : public UsbService,
private base::MessageLoop::DestructionObserver {
public:
explicit UsbServiceImpl(
PlatformUsbContext context,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner);
~UsbServiceImpl() override;
private:
// device::UsbService implementation
scoped_refptr<UsbDevice> GetDeviceById(uint32 unique_id) override;
void GetDevices(std::vector<scoped_refptr<UsbDevice>>* devices) override;
// base::MessageLoop::DestructionObserver implementation.
void WillDestroyCurrentMessageLoop() override;
// Enumerate USB devices from OS and update devices_ map.
void RefreshDevices();
// Adds a new UsbDevice to the devices_ map based on the given libusb device.
scoped_refptr<UsbDeviceImpl> AddDevice(PlatformUsbDevice platform_device);
// Handle hotplug events from libusb.
static int LIBUSB_CALL HotplugCallback(libusb_context* context,
PlatformUsbDevice device,
libusb_hotplug_event event,
void* user_data);
// These functions release a reference to the provided platform device.
void OnDeviceAdded(PlatformUsbDevice platform_device);
void OnDeviceRemoved(PlatformUsbDevice platform_device);
scoped_refptr<UsbContext> context_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
#if defined(OS_WIN)
class UIThreadHelper;
UIThreadHelper* ui_thread_helper_;
#endif // OS_WIN
// TODO(reillyg): Figure out a better solution for device IDs.
uint32 next_unique_id_;
// When available the device list will be updated when new devices are
// connected instead of only when a full enumeration is requested.
// TODO(reillyg): Support this on all platforms. crbug.com/411715
bool hotplug_enabled_;
libusb_hotplug_callback_handle hotplug_handle_;
// The map from unique IDs to UsbDevices.
typedef std::map<uint32, scoped_refptr<UsbDeviceImpl> > DeviceMap;
DeviceMap devices_;
// The map from PlatformUsbDevices to UsbDevices.
typedef std::map<PlatformUsbDevice, scoped_refptr<UsbDeviceImpl> >
PlatformDeviceMap;
PlatformDeviceMap platform_devices_;
base::WeakPtrFactory<UsbServiceImpl> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(UsbServiceImpl);
};
#if defined(OS_WIN)
// This class lives on the application main thread so that it can listen for
// device change notification window messages. It registers for notifications
@ -141,6 +63,23 @@ class UsbServiceImpl::UIThreadHelper final
};
#endif
// static
UsbService* UsbServiceImpl::Create(
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
PlatformUsbContext context = NULL;
const int rv = libusb_init(&context);
if (rv != LIBUSB_SUCCESS) {
VLOG(1) << "Failed to initialize libusb: "
<< ConvertPlatformUsbErrorToString(rv);
return nullptr;
}
if (!context) {
return nullptr;
}
return new UsbServiceImpl(context, ui_task_runner);
}
scoped_refptr<UsbDevice> UsbServiceImpl::GetDeviceById(uint32 unique_id) {
DCHECK(CalledOnValidThread());
RefreshDevices();
@ -165,11 +104,6 @@ void UsbServiceImpl::GetDevices(
}
}
void UsbServiceImpl::WillDestroyCurrentMessageLoop() {
DCHECK(CalledOnValidThread());
g_usb_service_instance.Get().reset(NULL);
}
UsbServiceImpl::UsbServiceImpl(
PlatformUsbContext context,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner)
@ -178,7 +112,6 @@ UsbServiceImpl::UsbServiceImpl(
next_unique_id_(0),
hotplug_enabled_(false),
weak_factory_(this) {
base::MessageLoop::current()->AddDestructionObserver(this);
task_runner_ = base::ThreadTaskRunnerHandle::Get();
int rv = libusb_hotplug_register_callback(
context_->context(),
@ -200,7 +133,6 @@ UsbServiceImpl::UsbServiceImpl(
}
UsbServiceImpl::~UsbServiceImpl() {
base::MessageLoop::current()->RemoveDestructionObserver(this);
if (hotplug_enabled_) {
libusb_hotplug_deregister_callback(context_->context(), hotplug_handle_);
}
@ -356,63 +288,4 @@ void UsbServiceImpl::OnDeviceRemoved(PlatformUsbDevice platform_device) {
libusb_unref_device(platform_device);
}
void UsbService::Observer::OnDeviceAdded(scoped_refptr<UsbDevice> device) {
}
void UsbService::Observer::OnDeviceRemoved(scoped_refptr<UsbDevice> device) {
}
// static
UsbService* UsbService::GetInstance(
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
UsbService* instance = g_usb_service_instance.Get().get();
if (!instance) {
PlatformUsbContext context = NULL;
const int rv = libusb_init(&context);
if (rv != LIBUSB_SUCCESS) {
VLOG(1) << "Failed to initialize libusb: "
<< ConvertPlatformUsbErrorToString(rv);
return NULL;
}
if (!context)
return NULL;
instance = new UsbServiceImpl(context, ui_task_runner);
g_usb_service_instance.Get().reset(instance);
}
return instance;
}
// static
void UsbService::SetInstanceForTest(UsbService* instance) {
g_usb_service_instance.Get().reset(instance);
}
UsbService::UsbService() {
}
UsbService::~UsbService() {
}
void UsbService::AddObserver(Observer* observer) {
DCHECK(CalledOnValidThread());
observer_list_.AddObserver(observer);
}
void UsbService::RemoveObserver(Observer* observer) {
DCHECK(CalledOnValidThread());
observer_list_.RemoveObserver(observer);
}
void UsbService::NotifyDeviceAdded(scoped_refptr<UsbDevice> device) {
DCHECK(CalledOnValidThread());
FOR_EACH_OBSERVER(Observer, observer_list_, OnDeviceAdded(device));
}
void UsbService::NotifyDeviceRemoved(scoped_refptr<UsbDevice> device) {
DCHECK(CalledOnValidThread());
FOR_EACH_OBSERVER(Observer, observer_list_, OnDeviceRemoved(device));
}
} // namespace device

@ -0,0 +1,82 @@
// Copyright 2015 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 "device/usb/usb_service.h"
#include <map>
#include "base/memory/weak_ptr.h"
#include "base/single_thread_task_runner.h"
#include "device/usb/usb_context.h"
#include "device/usb/usb_device_impl.h"
#include "third_party/libusb/src/libusb/libusb.h"
namespace device {
typedef struct libusb_device* PlatformUsbDevice;
typedef struct libusb_context* PlatformUsbContext;
class UsbServiceImpl : public UsbService {
public:
static UsbService* Create(
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner);
private:
explicit UsbServiceImpl(
PlatformUsbContext context,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner);
~UsbServiceImpl() override;
// device::UsbService implementation
scoped_refptr<UsbDevice> GetDeviceById(uint32 unique_id) override;
void GetDevices(std::vector<scoped_refptr<UsbDevice>>* devices) override;
// Enumerate USB devices from OS and update devices_ map.
void RefreshDevices();
// Adds a new UsbDevice to the devices_ map based on the given libusb device.
scoped_refptr<UsbDeviceImpl> AddDevice(PlatformUsbDevice platform_device);
// Handle hotplug events from libusb.
static int LIBUSB_CALL HotplugCallback(libusb_context* context,
PlatformUsbDevice device,
libusb_hotplug_event event,
void* user_data);
// These functions release a reference to the provided platform device.
void OnDeviceAdded(PlatformUsbDevice platform_device);
void OnDeviceRemoved(PlatformUsbDevice platform_device);
scoped_refptr<UsbContext> context_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
#if defined(OS_WIN)
class UIThreadHelper;
UIThreadHelper* ui_thread_helper_;
#endif // OS_WIN
// TODO(reillyg): Figure out a better solution for device IDs.
uint32 next_unique_id_;
// When available the device list will be updated when new devices are
// connected instead of only when a full enumeration is requested.
// TODO(reillyg): Support this on all platforms. crbug.com/411715
bool hotplug_enabled_;
libusb_hotplug_callback_handle hotplug_handle_;
// The map from unique IDs to UsbDevices.
typedef std::map<uint32, scoped_refptr<UsbDeviceImpl>> DeviceMap;
DeviceMap devices_;
// The map from PlatformUsbDevices to UsbDevices.
typedef std::map<PlatformUsbDevice, scoped_refptr<UsbDeviceImpl>>
PlatformDeviceMap;
PlatformDeviceMap platform_devices_;
base::WeakPtrFactory<UsbServiceImpl> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(UsbServiceImpl);
};
} // namespace device

@ -204,7 +204,7 @@ IN_PROC_BROWSER_TEST_F(HidApiTest, OnDeviceRemoved) {
ASSERT_TRUE(LoadApp("api_test/hid/remove_event"));
ASSERT_TRUE(load_listener.WaitUntilSatisfied());
// Device C was not returned by chrome.usb.getDevices, the app will not get
// Device C was not returned by chrome.hid.getDevices, the app will not get
// a notification.
hid_service_->RemoveDevice("C");
// Device A was returned, the app will get a notification.

@ -8,6 +8,7 @@
#include "device/usb/usb_service.h"
#include "extensions/browser/api/usb/usb_api.h"
#include "extensions/shell/test/shell_apitest.h"
#include "extensions/test/extension_test_message_listener.h"
#include "net/base/io_buffer.h"
#include "testing/gmock/include/gmock/gmock.h"
@ -126,6 +127,16 @@ class MockUsbService : public UsbService {
public:
explicit MockUsbService(scoped_refptr<UsbDevice> device) : device_(device) {}
// Public wrapper around the protected base class method.
void NotifyDeviceAdded(scoped_refptr<UsbDevice> device) {
UsbService::NotifyDeviceAdded(device);
}
// Public wrapper around the protected base class method.
void NotifyDeviceRemoved(scoped_refptr<UsbDevice> device) {
UsbService::NotifyDeviceRemoved(device);
}
protected:
scoped_refptr<UsbDevice> GetDeviceById(uint32 unique_id) override {
EXPECT_EQ(unique_id, 0U);
@ -144,8 +155,12 @@ class UsbApiTest : public ShellApiTest {
public:
void SetUpOnMainThread() override {
ShellApiTest::SetUpOnMainThread();
mock_device_handle_ = new MockUsbDeviceHandle();
mock_device_ = new MockUsbDevice(0, 0, 0);
EXPECT_CALL(*mock_device_.get(), GetSerialNumber(_))
.WillRepeatedly(Return(false));
mock_device_handle_ = new MockUsbDeviceHandle();
mock_device_handle_->set_device(mock_device_.get());
EXPECT_CALL(*mock_device_.get(), RequestUsbAccess(_, _))
.WillRepeatedly(Invoke(RequestUsbAccess));
@ -160,22 +175,26 @@ class UsbApiTest : public ShellApiTest {
}
void SetUpService() {
UsbService::SetInstanceForTest(new MockUsbService(mock_device_));
mock_service_ = new MockUsbService(mock_device_);
UsbService::SetInstanceForTest(mock_service_);
}
void TearDownOnMainThread() override {
UsbService* service = NULL;
base::RunLoop run_loop;
BrowserThread::PostTaskAndReply(
BrowserThread::FILE, FROM_HERE,
base::Bind(&UsbService::SetInstanceForTest, service),
run_loop.QuitClosure());
run_loop.Run();
void AddTestDevices() {
scoped_refptr<MockUsbDevice> device(new MockUsbDevice(0x18D1, 0x58F0, 1));
EXPECT_CALL(*device.get(), GetSerialNumber(_))
.WillRepeatedly(Return(false));
mock_service_->NotifyDeviceAdded(device);
device = new MockUsbDevice(0x18D1, 0x58F1, 2);
EXPECT_CALL(*device.get(), GetSerialNumber(_))
.WillRepeatedly(Return(false));
mock_service_->NotifyDeviceAdded(device);
}
protected:
scoped_refptr<MockUsbDeviceHandle> mock_device_handle_;
scoped_refptr<MockUsbDevice> mock_device_;
MockUsbService* mock_service_;
};
} // namespace
@ -263,4 +282,43 @@ IN_PROC_BROWSER_TEST_F(UsbApiTest, InvalidLengthTransfer) {
ASSERT_TRUE(RunAppTest("api_test/usb/invalid_length_transfer"));
}
IN_PROC_BROWSER_TEST_F(UsbApiTest, OnDeviceAdded) {
ExtensionTestMessageListener load_listener("loaded", false);
ExtensionTestMessageListener result_listener("success", false);
result_listener.set_failure_message("failure");
ASSERT_TRUE(LoadApp("api_test/usb/add_event"));
ASSERT_TRUE(load_listener.WaitUntilSatisfied());
base::RunLoop run_loop;
BrowserThread::PostTaskAndReply(
BrowserThread::FILE, FROM_HERE,
base::Bind(&UsbApiTest::AddTestDevices, base::Unretained(this)),
run_loop.QuitClosure());
run_loop.Run();
ASSERT_TRUE(result_listener.WaitUntilSatisfied());
ASSERT_EQ("success", result_listener.message());
}
IN_PROC_BROWSER_TEST_F(UsbApiTest, OnDeviceRemoved) {
ExtensionTestMessageListener load_listener("loaded", false);
ExtensionTestMessageListener result_listener("success", false);
result_listener.set_failure_message("failure");
ASSERT_TRUE(LoadApp("api_test/usb/remove_event"));
ASSERT_TRUE(load_listener.WaitUntilSatisfied());
base::RunLoop run_loop;
BrowserThread::PostTaskAndReply(
BrowserThread::FILE, FROM_HERE,
base::Bind(&MockUsbService::NotifyDeviceRemoved,
base::Unretained(mock_service_), mock_device_),
run_loop.QuitClosure());
run_loop.Run();
ASSERT_TRUE(result_listener.WaitUntilSatisfied());
ASSERT_EQ("success", result_listener.message());
}
} // namespace extensions

@ -0,0 +1,14 @@
// Copyright 2015 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.
chrome.usb.onDeviceAdded.addListener(function(device) {
if (device.vendorId == 6353 && device.productId == 22768) {
chrome.test.sendMessage("success");
} else {
console.error("Got unexpected device: vid:" + device.vendorId +
" pid:" + device.productId);
chrome.test.sendMessage("failure");
}
});
chrome.test.sendMessage("loaded");

@ -0,0 +1,15 @@
{
"name": "chrome.usb.onDeviceAdded",
"version": "0.1",
"description": "browser test for chrome.usb.onDeviceAdded event",
"app": {
"background": {
"scripts": ["background.js"]
}
},
"permissions": [
"usb",
// This is a test device emulated by the mocks enabled for the test.
{ "usbDevices": [{ "vendorId": 6353, "productId": 22768 }]}
]
}

@ -0,0 +1,21 @@
// Copyright 2014 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.
var known_devices = {};
chrome.usb.onDeviceRemoved.addListener(function(device) {
if (device.device in known_devices) {
chrome.test.sendMessage("success");
} else {
console.error("Unexpected device removed: " + device.device);
chrome.test.sendMessage("failure");
}
});
chrome.usb.getDevices({}, function(devices) {
for (var device of devices) {
known_devices[device.device] = device;
}
chrome.test.sendMessage("loaded");
});

@ -0,0 +1,15 @@
{
"name": "chrome.usb.onDeviceRemoved",
"version": "0.1",
"description": "browser test for chrome.usb.onDeviceRemoved event",
"app": {
"background": {
"scripts": ["background.js"]
}
},
"permissions": [
"usb",
// This is a test device emulated by the mocks enabled for the test.
{ "usbDevices": [{ "vendorId": 0, "productId": 0 }]}
]
}