Commit 22b8edda authored by Reilly Grant's avatar Reilly Grant Committed by Commit Bot

[serial] Implement connect and disconnect events

Bug: 918216
Change-Id: I0db59158a68ae89f1d1eb5fb92cd69dcad98381f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2095932
Commit-Queue: Reilly Grant <reillyg@chromium.org>
Reviewed-by: default avatarVincent Scheib <scheib@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#752732}
parent 9ee7bbea
...@@ -35,15 +35,19 @@ class SerialTest : public InProcessBrowserTest { ...@@ -35,15 +35,19 @@ class SerialTest : public InProcessBrowserTest {
mojo::PendingRemote<device::mojom::SerialPortManager> port_manager; mojo::PendingRemote<device::mojom::SerialPortManager> port_manager;
port_manager_.AddReceiver(port_manager.InitWithNewPipeAndPassReceiver()); port_manager_.AddReceiver(port_manager.InitWithNewPipeAndPassReceiver());
SerialChooserContextFactory::GetForProfile(browser()->profile()) context_ = SerialChooserContextFactory::GetForProfile(browser()->profile());
->SetPortManagerForTesting(std::move(port_manager)); context_->SetPortManagerForTesting(std::move(port_manager));
GURL url = embedded_test_server()->GetURL("localhost", "/simple_page.html"); GURL url = embedded_test_server()->GetURL("localhost", "/simple_page.html");
ui_test_utils::NavigateToURL(browser(), url); ui_test_utils::NavigateToURL(browser(), url);
} }
device::FakeSerialPortManager& port_manager() { return port_manager_; }
SerialChooserContext* context() { return context_; }
private: private:
device::FakeSerialPortManager port_manager_; device::FakeSerialPortManager port_manager_;
SerialChooserContext* context_;
}; };
IN_PROC_BROWSER_TEST_F(SerialTest, NavigateWithChooserCrossOrigin) { IN_PROC_BROWSER_TEST_F(SerialTest, NavigateWithChooserCrossOrigin) {
...@@ -62,4 +66,35 @@ IN_PROC_BROWSER_TEST_F(SerialTest, NavigateWithChooserCrossOrigin) { ...@@ -62,4 +66,35 @@ IN_PROC_BROWSER_TEST_F(SerialTest, NavigateWithChooserCrossOrigin) {
EXPECT_FALSE(chrome::IsDeviceChooserShowingForTesting(browser())); EXPECT_FALSE(chrome::IsDeviceChooserShowingForTesting(browser()));
} }
IN_PROC_BROWSER_TEST_F(SerialTest, RemovePort) {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Create port and grant permission to it.
auto port = device::mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
url::Origin origin = web_contents->GetMainFrame()->GetLastCommittedOrigin();
context()->GrantPortPermission(origin, origin, *port);
port_manager().AddPort(port.Clone());
// In order to ensure that the renderer is ready to receive events we must
// wait for the Promise returned by getPorts() to resolve before continuing.
EXPECT_EQ(true, content::EvalJs(web_contents, R"(
var removedPromise;
(async () => {
let ports = await navigator.serial.getPorts();
removedPromise = new Promise(resolve => {
navigator.serial.addEventListener(
'disconnect', e => {
resolve(e.port === ports[0]);
}, { once: true });
});
return true;
})())"));
port_manager().RemovePort(port->token);
EXPECT_EQ(true, content::EvalJs(web_contents, "removedPromise"));
}
} // namespace } // namespace
...@@ -15,6 +15,17 @@ ...@@ -15,6 +15,17 @@
#include "chrome/browser/ui/serial/serial_chooser_controller.h" #include "chrome/browser/ui/serial/serial_chooser_controller.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
namespace {
SerialChooserContext* GetChooserContext(content::RenderFrameHost* frame) {
auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
auto* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
return SerialChooserContextFactory::GetForProfile(profile);
}
} // namespace
ChromeSerialDelegate::ChromeSerialDelegate() = default; ChromeSerialDelegate::ChromeSerialDelegate() = default;
ChromeSerialDelegate::~ChromeSerialDelegate() = default; ChromeSerialDelegate::~ChromeSerialDelegate() = default;
...@@ -53,9 +64,15 @@ bool ChromeSerialDelegate::HasPortPermission( ...@@ -53,9 +64,15 @@ bool ChromeSerialDelegate::HasPortPermission(
device::mojom::SerialPortManager* ChromeSerialDelegate::GetPortManager( device::mojom::SerialPortManager* ChromeSerialDelegate::GetPortManager(
content::RenderFrameHost* frame) { content::RenderFrameHost* frame) {
auto* web_contents = content::WebContents::FromRenderFrameHost(frame); return GetChooserContext(frame)->GetPortManager();
auto* profile = }
Profile::FromBrowserContext(web_contents->GetBrowserContext());
auto* chooser_context = SerialChooserContextFactory::GetForProfile(profile); void ChromeSerialDelegate::AddObserver(content::RenderFrameHost* frame,
return chooser_context->GetPortManager(); Observer* observer) {
return GetChooserContext(frame)->AddPortObserver(observer);
}
void ChromeSerialDelegate::RemoveObserver(content::RenderFrameHost* frame,
Observer* observer) {
return GetChooserContext(frame)->RemovePortObserver(observer);
} }
...@@ -24,6 +24,10 @@ class ChromeSerialDelegate : public content::SerialDelegate { ...@@ -24,6 +24,10 @@ class ChromeSerialDelegate : public content::SerialDelegate {
const device::mojom::SerialPortInfo& port) override; const device::mojom::SerialPortInfo& port) override;
device::mojom::SerialPortManager* GetPortManager( device::mojom::SerialPortManager* GetPortManager(
content::RenderFrameHost* frame) override; content::RenderFrameHost* frame) override;
void AddObserver(content::RenderFrameHost* frame,
Observer* observer) override;
void RemoveObserver(content::RenderFrameHost* frame,
Observer* observer) override;
private: private:
DISALLOW_COPY_AND_ASSIGN(ChromeSerialDelegate); DISALLOW_COPY_AND_ASSIGN(ChromeSerialDelegate);
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/unguessable_token.h" #include "base/unguessable_token.h"
#include "components/permissions/chooser_context_base.h" #include "components/permissions/chooser_context_base.h"
#include "content/public/browser/serial_delegate.h"
#include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/bindings/remote.h"
#include "services/device/public/mojom/serial.mojom-forward.h" #include "services/device/public/mojom/serial.mojom-forward.h"
...@@ -31,12 +32,7 @@ class Value; ...@@ -31,12 +32,7 @@ class Value;
class SerialChooserContext : public permissions::ChooserContextBase, class SerialChooserContext : public permissions::ChooserContextBase,
public device::mojom::SerialPortManagerClient { public device::mojom::SerialPortManagerClient {
public: public:
class PortObserver : public base::CheckedObserver { using PortObserver = content::SerialDelegate::Observer;
public:
virtual void OnPortAdded(const device::mojom::SerialPortInfo& port) = 0;
virtual void OnPortRemoved(const device::mojom::SerialPortInfo& port) = 0;
virtual void OnPortManagerConnectionError() = 0;
};
explicit SerialChooserContext(Profile* profile); explicit SerialChooserContext(Profile* profile);
~SerialChooserContext() override; ~SerialChooserContext() override;
......
...@@ -51,9 +51,17 @@ SerialService::SerialService(RenderFrameHost* render_frame_host) ...@@ -51,9 +51,17 @@ SerialService::SerialService(RenderFrameHost* render_frame_host)
watchers_.set_disconnect_handler(base::BindRepeating( watchers_.set_disconnect_handler(base::BindRepeating(
&SerialService::OnWatcherConnectionError, base::Unretained(this))); &SerialService::OnWatcherConnectionError, base::Unretained(this)));
SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
if (delegate)
delegate->AddObserver(render_frame_host_, this);
} }
SerialService::~SerialService() { SerialService::~SerialService() {
SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
if (delegate)
delegate->RemoveObserver(render_frame_host_, this);
// The remaining watchers will be closed from this end. // The remaining watchers will be closed from this end.
if (!watchers_.empty()) if (!watchers_.empty())
DecrementActiveFrameCount(); DecrementActiveFrameCount();
...@@ -64,6 +72,11 @@ void SerialService::Bind( ...@@ -64,6 +72,11 @@ void SerialService::Bind(
receivers_.Add(this, std::move(receiver)); receivers_.Add(this, std::move(receiver));
} }
void SerialService::SetClient(
mojo::PendingRemote<blink::mojom::SerialServiceClient> client) {
clients_.Add(std::move(client));
}
void SerialService::GetPorts(GetPortsCallback callback) { void SerialService::GetPorts(GetPortsCallback callback) {
SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate(); SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
if (!delegate) { if (!delegate) {
...@@ -116,6 +129,38 @@ void SerialService::GetPort( ...@@ -116,6 +129,38 @@ void SerialService::GetPort(
->GetPort(token, std::move(receiver), std::move(watcher)); ->GetPort(token, std::move(receiver), std::move(watcher));
} }
void SerialService::OnPortAdded(const device::mojom::SerialPortInfo& port) {
SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
if (!delegate || !delegate->HasPortPermission(render_frame_host_, port))
return;
for (const auto& client : clients_)
client->OnPortAdded(ToBlinkType(port));
}
void SerialService::OnPortRemoved(const device::mojom::SerialPortInfo& port) {
SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
if (!delegate || !delegate->HasPortPermission(render_frame_host_, port))
return;
for (const auto& client : clients_)
client->OnPortRemoved(ToBlinkType(port));
}
void SerialService::OnPortManagerConnectionError() {
// Reflect the loss of the SerialPortManager connection into the renderer
// in order to force caches to be cleared and connections to be
// re-established.
receivers_.Clear();
clients_.Clear();
chooser_.reset();
if (!watchers_.empty()) {
watchers_.Clear();
DecrementActiveFrameCount();
}
}
void SerialService::FinishGetPorts( void SerialService::FinishGetPorts(
GetPortsCallback callback, GetPortsCallback callback,
std::vector<device::mojom::SerialPortInfoPtr> ports) { std::vector<device::mojom::SerialPortInfoPtr> ports) {
......
...@@ -10,8 +10,10 @@ ...@@ -10,8 +10,10 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "content/public/browser/serial_delegate.h"
#include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h" #include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote_set.h"
#include "services/device/public/mojom/serial.mojom.h" #include "services/device/public/mojom/serial.mojom.h"
#include "third_party/blink/public/mojom/serial/serial.mojom.h" #include "third_party/blink/public/mojom/serial/serial.mojom.h"
...@@ -21,6 +23,7 @@ class RenderFrameHost; ...@@ -21,6 +23,7 @@ class RenderFrameHost;
class SerialChooser; class SerialChooser;
class SerialService : public blink::mojom::SerialService, class SerialService : public blink::mojom::SerialService,
public SerialDelegate::Observer,
public device::mojom::SerialPortConnectionWatcher { public device::mojom::SerialPortConnectionWatcher {
public: public:
explicit SerialService(RenderFrameHost* render_frame_host); explicit SerialService(RenderFrameHost* render_frame_host);
...@@ -29,6 +32,8 @@ class SerialService : public blink::mojom::SerialService, ...@@ -29,6 +32,8 @@ class SerialService : public blink::mojom::SerialService,
void Bind(mojo::PendingReceiver<blink::mojom::SerialService> receiver); void Bind(mojo::PendingReceiver<blink::mojom::SerialService> receiver);
// SerialService implementation // SerialService implementation
void SetClient(
mojo::PendingRemote<blink::mojom::SerialServiceClient> client) override;
void GetPorts(GetPortsCallback callback) override; void GetPorts(GetPortsCallback callback) override;
void RequestPort(std::vector<blink::mojom::SerialPortFilterPtr> filters, void RequestPort(std::vector<blink::mojom::SerialPortFilterPtr> filters,
RequestPortCallback callback) override; RequestPortCallback callback) override;
...@@ -36,6 +41,11 @@ class SerialService : public blink::mojom::SerialService, ...@@ -36,6 +41,11 @@ class SerialService : public blink::mojom::SerialService,
const base::UnguessableToken& token, const base::UnguessableToken& token,
mojo::PendingReceiver<device::mojom::SerialPort> receiver) override; mojo::PendingReceiver<device::mojom::SerialPort> receiver) override;
// SerialDelegate::Observer implementation
void OnPortAdded(const device::mojom::SerialPortInfo& port) override;
void OnPortRemoved(const device::mojom::SerialPortInfo& port) override;
void OnPortManagerConnectionError() override;
private: private:
void FinishGetPorts(GetPortsCallback callback, void FinishGetPorts(GetPortsCallback callback,
std::vector<device::mojom::SerialPortInfoPtr> ports); std::vector<device::mojom::SerialPortInfoPtr> ports);
...@@ -48,6 +58,7 @@ class SerialService : public blink::mojom::SerialService, ...@@ -48,6 +58,7 @@ class SerialService : public blink::mojom::SerialService,
// RenderFrameHostImpl. // RenderFrameHostImpl.
RenderFrameHost* const render_frame_host_; RenderFrameHost* const render_frame_host_;
mojo::ReceiverSet<blink::mojom::SerialService> receivers_; mojo::ReceiverSet<blink::mojom::SerialService> receivers_;
mojo::RemoteSet<blink::mojom::SerialServiceClient> clients_;
// The last shown serial port chooser UI. // The last shown serial port chooser UI.
std::unique_ptr<SerialChooser> chooser_; std::unique_ptr<SerialChooser> chooser_;
......
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
#ifndef CONTENT_BROWSER_SERIAL_SERIAL_TEST_UTILS_H_ #ifndef CONTENT_BROWSER_SERIAL_SERIAL_TEST_UTILS_H_
#define CONTENT_BROWSER_SERIAL_SERIAL_TEST_UTILS_H_ #define CONTENT_BROWSER_SERIAL_SERIAL_TEST_UTILS_H_
#include <memory>
#include <vector>
#include "content/public/browser/content_browser_client.h" #include "content/public/browser/content_browser_client.h"
#include "content/public/browser/serial_delegate.h" #include "content/public/browser/serial_delegate.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
...@@ -24,11 +27,13 @@ class MockSerialDelegate : public SerialDelegate { ...@@ -24,11 +27,13 @@ class MockSerialDelegate : public SerialDelegate {
MOCK_METHOD0(RunChooserInternal, device::mojom::SerialPortInfoPtr()); MOCK_METHOD0(RunChooserInternal, device::mojom::SerialPortInfoPtr());
MOCK_METHOD1(CanRequestPortPermission, bool(RenderFrameHost* frame)); MOCK_METHOD1(CanRequestPortPermission, bool(RenderFrameHost* frame));
MOCK_METHOD2(HasPortPermission, MOCK_METHOD2(HasPortPermission,
bool(content::RenderFrameHost* frame, bool(RenderFrameHost* frame,
const device::mojom::SerialPortInfo& port)); const device::mojom::SerialPortInfo& port));
MOCK_METHOD1( MOCK_METHOD1(GetPortManager,
GetPortManager, device::mojom::SerialPortManager*(RenderFrameHost* frame));
device::mojom::SerialPortManager*(content::RenderFrameHost* frame)); MOCK_METHOD2(AddObserver, void(RenderFrameHost* frame, Observer* observer));
MOCK_METHOD2(RemoveObserver,
void(RenderFrameHost* frame, Observer* observer));
private: private:
DISALLOW_COPY_AND_ASSIGN(MockSerialDelegate); DISALLOW_COPY_AND_ASSIGN(MockSerialDelegate);
......
...@@ -2,7 +2,10 @@ ...@@ -2,7 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "base/barrier_closure.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/run_loop.h"
#include "base/test/gmock_callback_support.h"
#include "content/browser/serial/serial_test_utils.h" #include "content/browser/serial/serial_test_utils.h"
#include "content/public/common/content_client.h" #include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h" #include "content/public/common/content_switches.h"
...@@ -13,6 +16,9 @@ ...@@ -13,6 +16,9 @@
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::Return;
namespace content { namespace content {
namespace { namespace {
...@@ -20,11 +26,36 @@ namespace { ...@@ -20,11 +26,36 @@ namespace {
const char kTestUrl[] = "https://www.google.com"; const char kTestUrl[] = "https://www.google.com";
const char kCrossOriginTestUrl[] = "https://www.chromium.org"; const char kCrossOriginTestUrl[] = "https://www.chromium.org";
class MockSerialServiceClient : public blink::mojom::SerialServiceClient {
public:
MockSerialServiceClient() = default;
MockSerialServiceClient(const MockSerialServiceClient&) = delete;
MockSerialServiceClient& operator=(const MockSerialServiceClient&) = delete;
~MockSerialServiceClient() override {
// Flush the pipe to make sure there aren't any lingering events.
receiver_.FlushForTesting();
}
mojo::PendingRemote<blink::mojom::SerialServiceClient>
BindNewPipeAndPassRemote() {
return receiver_.BindNewPipeAndPassRemote();
}
// mojom::SerialPortManagerClient
MOCK_METHOD1(OnPortAdded, void(blink::mojom::SerialPortInfoPtr));
MOCK_METHOD1(OnPortRemoved, void(blink::mojom::SerialPortInfoPtr));
private:
mojo::Receiver<blink::mojom::SerialServiceClient> receiver_{this};
};
class SerialTest : public RenderViewHostImplTestHarness { class SerialTest : public RenderViewHostImplTestHarness {
public: public:
SerialTest() { SerialTest() {
ON_CALL(test_client_.delegate(), GetPortManager) ON_CALL(delegate(), GetPortManager).WillByDefault(Return(&port_manager_));
.WillByDefault(testing::Return(&port_manager_)); ON_CALL(delegate(), AddObserver)
.WillByDefault(testing::SaveArg<1>(&observer_));
} }
~SerialTest() override = default; ~SerialTest() override = default;
...@@ -42,12 +73,15 @@ class SerialTest : public RenderViewHostImplTestHarness { ...@@ -42,12 +73,15 @@ class SerialTest : public RenderViewHostImplTestHarness {
SetBrowserClientForTesting(original_client_); SetBrowserClientForTesting(original_client_);
} }
MockSerialDelegate& delegate() { return test_client_.delegate(); }
device::FakeSerialPortManager* port_manager() { return &port_manager_; } device::FakeSerialPortManager* port_manager() { return &port_manager_; }
SerialDelegate::Observer* observer() { return observer_; }
private: private:
SerialTestContentBrowserClient test_client_; SerialTestContentBrowserClient test_client_;
ContentBrowserClient* original_client_ = nullptr; ContentBrowserClient* original_client_ = nullptr;
device::FakeSerialPortManager port_manager_; device::FakeSerialPortManager port_manager_;
SerialDelegate::Observer* observer_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(SerialTest); DISALLOW_COPY_AND_ASSIGN(SerialTest);
}; };
...@@ -104,4 +138,86 @@ TEST_F(SerialTest, OpenAndNavigateCrossOrigin) { ...@@ -104,4 +138,86 @@ TEST_F(SerialTest, OpenAndNavigateCrossOrigin) {
EXPECT_FALSE(port.is_connected()); EXPECT_FALSE(port.is_connected());
} }
TEST_F(SerialTest, AddAndRemovePorts) {
mojo::Remote<blink::mojom::SerialService> service;
contents()->GetMainFrame()->BindSerialService(
service.BindNewPipeAndPassReceiver());
MockSerialServiceClient client;
service->SetClient(client.BindNewPipeAndPassRemote());
service.FlushForTesting();
ASSERT_TRUE(observer());
// Three ports will be added and then removed. Only the 1st and 3rd will have
// permission granted.
std::vector<device::mojom::SerialPortInfoPtr> ports;
for (size_t i = 0; i < 3; i++) {
auto port = device::mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
ports.push_back(std::move(port));
}
EXPECT_CALL(delegate(), HasPortPermission(_, _))
.WillOnce(Return(true))
.WillOnce(Return(false))
.WillOnce(Return(true))
.WillOnce(Return(true))
.WillOnce(Return(false))
.WillOnce(Return(true));
{
base::RunLoop run_loop;
auto closure = base::BarrierClosure(2, run_loop.QuitClosure());
EXPECT_CALL(client, OnPortAdded(_))
.Times(2)
.WillRepeatedly(base::test::RunClosure(closure));
for (const auto& port : ports)
observer()->OnPortAdded(*port);
run_loop.Run();
}
{
base::RunLoop run_loop;
auto closure = base::BarrierClosure(2, run_loop.QuitClosure());
EXPECT_CALL(client, OnPortRemoved(_))
.Times(2)
.WillRepeatedly(base::test::RunClosure(closure));
for (const auto& port : ports)
observer()->OnPortRemoved(*port);
run_loop.Run();
}
}
TEST_F(SerialTest, OpenAndClosePortManagerConnection) {
NavigateAndCommit(GURL(kTestUrl));
mojo::Remote<blink::mojom::SerialService> service;
contents()->GetMainFrame()->BindSerialService(
service.BindNewPipeAndPassReceiver());
auto token = base::UnguessableToken::Create();
auto port_info = device::mojom::SerialPortInfo::New();
port_info->token = token;
port_manager()->AddPort(std::move(port_info));
EXPECT_FALSE(contents()->IsConnectedToSerialPort());
mojo::Remote<device::mojom::SerialPort> port;
service->GetPort(token, port.BindNewPipeAndPassReceiver());
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(contents()->IsConnectedToSerialPort());
ASSERT_TRUE(observer());
observer()->OnPortManagerConnectionError();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(contents()->IsConnectedToSerialPort());
port.FlushForTesting();
EXPECT_FALSE(port.is_connected());
service.FlushForTesting();
EXPECT_FALSE(service.is_connected());
}
} // namespace content } // namespace content
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "base/observer_list_types.h"
#include "content/common/content_export.h" #include "content/common/content_export.h"
#include "content/public/browser/serial_chooser.h" #include "content/public/browser/serial_chooser.h"
#include "services/device/public/mojom/serial.mojom.h" #include "services/device/public/mojom/serial.mojom.h"
...@@ -19,6 +20,13 @@ class RenderFrameHost; ...@@ -19,6 +20,13 @@ class RenderFrameHost;
class CONTENT_EXPORT SerialDelegate { class CONTENT_EXPORT SerialDelegate {
public: public:
class Observer : public base::CheckedObserver {
public:
virtual void OnPortAdded(const device::mojom::SerialPortInfo& port) = 0;
virtual void OnPortRemoved(const device::mojom::SerialPortInfo& port) = 0;
virtual void OnPortManagerConnectionError() = 0;
};
virtual ~SerialDelegate() = default; virtual ~SerialDelegate() = default;
// Shows a chooser for the user to select a serial port. |callback| will be // Shows a chooser for the user to select a serial port. |callback| will be
...@@ -46,6 +54,11 @@ class CONTENT_EXPORT SerialDelegate { ...@@ -46,6 +54,11 @@ class CONTENT_EXPORT SerialDelegate {
// possible. // possible.
virtual device::mojom::SerialPortManager* GetPortManager( virtual device::mojom::SerialPortManager* GetPortManager(
RenderFrameHost* frame) = 0; RenderFrameHost* frame) = 0;
// Functions to manage the set of Observer instances registered to this
// object.
virtual void AddObserver(RenderFrameHost* frame, Observer* observer) = 0;
virtual void RemoveObserver(RenderFrameHost* frame, Observer* observer) = 0;
}; };
} // namespace content } // namespace content
......
...@@ -26,7 +26,13 @@ struct SerialPortFilter { ...@@ -26,7 +26,13 @@ struct SerialPortFilter {
bool has_product_id; bool has_product_id;
}; };
// This interface must be provided by the browser process in order for the
// renderer process to implement the navigator.serial API.
interface SerialService { interface SerialService {
// Sets the client which will receive notifications of events from this
// service, such as when ports are added and removed from the system.
SetClient(pending_remote<SerialServiceClient> client);
// Retrieves information about all ports available to this client. // Retrieves information about all ports available to this client.
GetPorts() => (array<SerialPortInfo> ports); GetPorts() => (array<SerialPortInfo> ports);
...@@ -38,3 +44,12 @@ interface SerialService { ...@@ -38,3 +44,12 @@ interface SerialService {
GetPort(mojo_base.mojom.UnguessableToken token, GetPort(mojo_base.mojom.UnguessableToken token,
pending_receiver<device.mojom.SerialPort> port_receiver); pending_receiver<device.mojom.SerialPort> port_receiver);
}; };
// Client interface for receiving events from SerialService.
interface SerialServiceClient {
// Called when a port described by |port_info| is added to the system.
OnPortAdded(SerialPortInfo port_info);
// Called when a port described by |port_info| is removed from the system.
OnPortRemoved(SerialPortInfo port_info);
};
...@@ -14,6 +14,8 @@ blink_modules_sources("serial") { ...@@ -14,6 +14,8 @@ blink_modules_sources("serial") {
"navigator_serial.h", "navigator_serial.h",
"serial.cc", "serial.cc",
"serial.h", "serial.h",
"serial_connection_event.cc",
"serial_connection_event.h",
"serial_port.cc", "serial_port.cc",
"serial_port.h", "serial_port.h",
"serial_port_underlying_sink.cc", "serial_port_underlying_sink.cc",
......
...@@ -4,10 +4,12 @@ ...@@ -4,10 +4,12 @@
modules_idl_files = [ modules_idl_files = [
"serial.idl", "serial.idl",
"serial_connection_event.idl",
"serial_port.idl", "serial_port.idl",
] ]
modules_dictionary_idl_files = [ modules_dictionary_idl_files = [
"serial_connection_event_init.idl",
"serial_input_signals.idl", "serial_input_signals.idl",
"serial_options.idl", "serial_options.idl",
"serial_output_signals.idl", "serial_output_signals.idl",
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "third_party/blink/renderer/core/execution_context/execution_context.h" #include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/modules/event_target_modules_names.h" #include "third_party/blink/renderer/modules/event_target_modules_names.h"
#include "third_party/blink/renderer/modules/serial/serial_connection_event.h"
#include "third_party/blink/renderer/modules/serial/serial_port.h" #include "third_party/blink/renderer/modules/serial/serial_port.h"
#include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/heap/heap.h"
...@@ -56,6 +57,16 @@ void Serial::ContextDestroyed() { ...@@ -56,6 +57,16 @@ void Serial::ContextDestroyed() {
entry.value->ContextDestroyed(); entry.value->ContextDestroyed();
} }
void Serial::OnPortAdded(mojom::blink::SerialPortInfoPtr port_info) {
DispatchEvent(*SerialConnectionEvent::Create(
event_type_names::kConnect, GetOrCreatePort(std::move(port_info))));
}
void Serial::OnPortRemoved(mojom::blink::SerialPortInfoPtr port_info) {
DispatchEvent(*SerialConnectionEvent::Create(
event_type_names::kDisconnect, GetOrCreatePort(std::move(port_info))));
}
ScriptPromise Serial::getPorts(ScriptState* script_state, ScriptPromise Serial::getPorts(ScriptState* script_state,
ExceptionState& exception_state) { ExceptionState& exception_state) {
auto* context = GetExecutionContext(); auto* context = GetExecutionContext();
...@@ -144,6 +155,10 @@ ScriptPromise Serial::requestPort(ScriptState* script_state, ...@@ -144,6 +155,10 @@ ScriptPromise Serial::requestPort(ScriptState* script_state,
return resolver->Promise(); return resolver->Promise();
} }
void Serial::Dispose() {
receiver_.reset();
}
void Serial::GetPort( void Serial::GetPort(
const base::UnguessableToken& token, const base::UnguessableToken& token,
mojo::PendingReceiver<device::mojom::blink::SerialPort> receiver) { mojo::PendingReceiver<device::mojom::blink::SerialPort> receiver) {
...@@ -159,6 +174,25 @@ void Serial::Trace(Visitor* visitor) { ...@@ -159,6 +174,25 @@ void Serial::Trace(Visitor* visitor) {
ExecutionContextLifecycleObserver::Trace(visitor); ExecutionContextLifecycleObserver::Trace(visitor);
} }
void Serial::AddedEventListener(const AtomicString& event_type,
RegisteredEventListener& listener) {
EventTargetWithInlineData::AddedEventListener(event_type, listener);
if (event_type != event_type_names::kConnect &&
event_type != event_type_names::kDisconnect) {
return;
}
ExecutionContext* context = GetExecutionContext();
if (!context ||
!context->IsFeatureEnabled(mojom::blink::FeaturePolicyFeature::kSerial,
ReportOptions::kDoNotReport)) {
return;
}
EnsureServiceConnection();
}
void Serial::EnsureServiceConnection() { void Serial::EnsureServiceConnection() {
DCHECK(GetExecutionContext()); DCHECK(GetExecutionContext());
...@@ -171,10 +205,13 @@ void Serial::EnsureServiceConnection() { ...@@ -171,10 +205,13 @@ void Serial::EnsureServiceConnection() {
service_.BindNewPipeAndPassReceiver(task_runner)); service_.BindNewPipeAndPassReceiver(task_runner));
service_.set_disconnect_handler( service_.set_disconnect_handler(
WTF::Bind(&Serial::OnServiceConnectionError, WrapWeakPersistent(this))); WTF::Bind(&Serial::OnServiceConnectionError, WrapWeakPersistent(this)));
service_->SetClient(receiver_.BindNewPipeAndPassRemote());
} }
void Serial::OnServiceConnectionError() { void Serial::OnServiceConnectionError() {
service_.reset(); service_.reset();
receiver_.reset();
// Script may execute during a call to Resolve(). Swap these sets to prevent // Script may execute during a call to Resolve(). Swap these sets to prevent
// concurrent modification. // concurrent modification.
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_SERIAL_SERIAL_H_ #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_SERIAL_SERIAL_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_SERIAL_SERIAL_H_ #define THIRD_PARTY_BLINK_RENDERER_MODULES_SERIAL_SERIAL_H_
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/bindings/remote.h"
#include "third_party/blink/public/mojom/serial/serial.mojom-blink.h" #include "third_party/blink/public/mojom/serial/serial.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h" #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
...@@ -24,9 +25,11 @@ class SerialPort; ...@@ -24,9 +25,11 @@ class SerialPort;
class SerialPortRequestOptions; class SerialPortRequestOptions;
class Serial final : public EventTargetWithInlineData, class Serial final : public EventTargetWithInlineData,
public ExecutionContextLifecycleObserver { public ExecutionContextLifecycleObserver,
public mojom::blink::SerialServiceClient {
DEFINE_WRAPPERTYPEINFO(); DEFINE_WRAPPERTYPEINFO();
USING_GARBAGE_COLLECTED_MIXIN(Serial); USING_GARBAGE_COLLECTED_MIXIN(Serial);
USING_PRE_FINALIZER(Serial, Dispose);
public: public:
explicit Serial(ExecutionContext&); explicit Serial(ExecutionContext&);
...@@ -38,6 +41,10 @@ class Serial final : public EventTargetWithInlineData, ...@@ -38,6 +41,10 @@ class Serial final : public EventTargetWithInlineData,
// ExecutionContextLifecycleObserver // ExecutionContextLifecycleObserver
void ContextDestroyed() override; void ContextDestroyed() override;
// SerialServiceClient
void OnPortAdded(mojom::blink::SerialPortInfoPtr port_info) override;
void OnPortRemoved(mojom::blink::SerialPortInfoPtr port_info) override;
// Web-exposed interfaces // Web-exposed interfaces
DEFINE_ATTRIBUTE_EVENT_LISTENER(connect, kConnect) DEFINE_ATTRIBUTE_EVENT_LISTENER(connect, kConnect)
DEFINE_ATTRIBUTE_EVENT_LISTENER(disconnect, kDisconnect) DEFINE_ATTRIBUTE_EVENT_LISTENER(disconnect, kDisconnect)
...@@ -46,11 +53,17 @@ class Serial final : public EventTargetWithInlineData, ...@@ -46,11 +53,17 @@ class Serial final : public EventTargetWithInlineData,
const SerialPortRequestOptions*, const SerialPortRequestOptions*,
ExceptionState&); ExceptionState&);
void Dispose();
void GetPort( void GetPort(
const base::UnguessableToken& token, const base::UnguessableToken& token,
mojo::PendingReceiver<device::mojom::blink::SerialPort> receiver); mojo::PendingReceiver<device::mojom::blink::SerialPort> receiver);
void Trace(Visitor*) override; void Trace(Visitor*) override;
protected:
// EventTarget
void AddedEventListener(const AtomicString& event_type,
RegisteredEventListener&) override;
private: private:
void EnsureServiceConnection(); void EnsureServiceConnection();
void OnServiceConnectionError(); void OnServiceConnectionError();
...@@ -60,6 +73,7 @@ class Serial final : public EventTargetWithInlineData, ...@@ -60,6 +73,7 @@ class Serial final : public EventTargetWithInlineData,
void OnRequestPort(ScriptPromiseResolver*, mojom::blink::SerialPortInfoPtr); void OnRequestPort(ScriptPromiseResolver*, mojom::blink::SerialPortInfoPtr);
mojo::Remote<mojom::blink::SerialService> service_; mojo::Remote<mojom::blink::SerialService> service_;
mojo::Receiver<mojom::blink::SerialServiceClient> receiver_{this};
HeapHashSet<Member<ScriptPromiseResolver>> get_ports_promises_; HeapHashSet<Member<ScriptPromiseResolver>> get_ports_promises_;
HeapHashSet<Member<ScriptPromiseResolver>> request_port_promises_; HeapHashSet<Member<ScriptPromiseResolver>> request_port_promises_;
HeapHashMap<String, WeakMember<SerialPort>> port_cache_; HeapHashMap<String, WeakMember<SerialPort>> port_cache_;
......
// Copyright 2020 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 "third_party/blink/renderer/modules/serial/serial_connection_event.h"
#include "third_party/blink/renderer/modules/serial/serial_connection_event_init.h"
#include "third_party/blink/renderer/modules/serial/serial_port.h"
namespace blink {
SerialConnectionEvent* SerialConnectionEvent::Create(
const AtomicString& type,
const SerialConnectionEventInit* initializer) {
return MakeGarbageCollected<SerialConnectionEvent>(type, initializer);
}
SerialConnectionEvent* SerialConnectionEvent::Create(const AtomicString& type,
SerialPort* port) {
return MakeGarbageCollected<SerialConnectionEvent>(type, port);
}
SerialConnectionEvent::SerialConnectionEvent(
const AtomicString& type,
const SerialConnectionEventInit* initializer)
: Event(type, initializer), port_(initializer->port()) {}
SerialConnectionEvent::SerialConnectionEvent(const AtomicString& type,
SerialPort* port)
: Event(type, Bubbles::kNo, Cancelable::kNo), port_(port) {}
void SerialConnectionEvent::Trace(Visitor* visitor) {
visitor->Trace(port_);
Event::Trace(visitor);
}
} // namespace blink
// Copyright 2020 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_SERIAL_SERIAL_CONNECTION_EVENT_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_SERIAL_SERIAL_CONNECTION_EVENT_H_
#include "third_party/blink/renderer/modules/event_modules.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
namespace blink {
class SerialConnectionEventInit;
class SerialPort;
class SerialConnectionEvent final : public Event {
DEFINE_WRAPPERTYPEINFO();
public:
static SerialConnectionEvent* Create(const AtomicString& type,
const SerialConnectionEventInit*);
static SerialConnectionEvent* Create(const AtomicString& type, SerialPort*);
SerialConnectionEvent(const AtomicString& type,
const SerialConnectionEventInit*);
SerialConnectionEvent(const AtomicString& type, SerialPort*);
SerialPort* port() const { return port_; }
void Trace(Visitor*) override;
private:
Member<SerialPort> port_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_SERIAL_SERIAL_CONNECTION_EVENT_H_
// Copyright 2020 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.
[
Exposed(Window Serial, DedicatedWorker Serial),
SecureContext
] interface SerialConnectionEvent : Event {
constructor(DOMString type, SerialConnectionEventInit eventInitDict);
[SameObject] readonly attribute SerialPort port;
};
// Copyright 2020 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.
dictionary SerialConnectionEventInit : EventInit {
required SerialPort port;
};
...@@ -307,6 +307,7 @@ class FakeSerialService { ...@@ -307,6 +307,7 @@ class FakeSerialService {
new MojoInterfaceInterceptor(blink.mojom.SerialService.name); new MojoInterfaceInterceptor(blink.mojom.SerialService.name);
this.interceptor_.oninterfacerequest = e => this.bind(e.handle); this.interceptor_.oninterfacerequest = e => this.bind(e.handle);
this.bindingSet_ = new mojo.BindingSet(blink.mojom.SerialService); this.bindingSet_ = new mojo.BindingSet(blink.mojom.SerialService);
this.clients_ = [];
this.nextToken_ = 0; this.nextToken_ = 0;
this.reset(); this.reset();
} }
...@@ -343,11 +344,25 @@ class FakeSerialService { ...@@ -343,11 +344,25 @@ class FakeSerialService {
fakePort: new FakeSerialPort(), fakePort: new FakeSerialPort(),
}; };
this.ports_.set(token, record); this.ports_.set(token, record);
for (let client of this.clients_) {
client.onPortAdded(info);
}
return token; return token;
} }
removePort(token) { removePort(token) {
let record = this.ports_.get(token);
if (record === undefined) {
return;
}
this.ports_.delete(token); this.ports_.delete(token);
for (let client of this.clients_) {
client.onPortRemoved(record.portInfo);
}
} }
setSelectedPort(token) { setSelectedPort(token) {
...@@ -365,6 +380,10 @@ class FakeSerialService { ...@@ -365,6 +380,10 @@ class FakeSerialService {
this.bindingSet_.addBinding(this, handle); this.bindingSet_.addBinding(this, handle);
} }
async setClient(client_remote) {
this.clients_.push(client_remote);
}
async getPorts() { async getPorts() {
return { return {
ports: Array.from(this.ports_, ([token, record]) => record.portInfo) ports: Array.from(this.ports_, ([token, record]) => record.portInfo)
......
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
<script src="file:///gen/mojo/public/mojom/base/unguessable_token.mojom.js"></script>
<script src="file:///gen/third_party/blink/public/mojom/serial/serial.mojom.js"></script>
<script src="resources/serial-test-utils.js"></script>
<script>
serial_test(async (t, fake) => {
const eventWatcher =
new EventWatcher(t, navigator.serial, ['connect', 'disconnect']);
// Wait for getPorts() to resolve in order to ensure that the Mojo client
// interface has been configured.
let ports = await navigator.serial.getPorts();
assert_equals(ports.length, 0);
fake.addPort();
const event1 = await eventWatcher.wait_for(['connect']);
assert_true(event1 instanceof SerialConnectionEvent);
assert_true(event1.port instanceof SerialPort);
fake.addPort();
const event2 = await eventWatcher.wait_for(['connect']);
assert_true(event2 instanceof SerialConnectionEvent);
assert_true(event2.port instanceof SerialPort);
assert_not_equals(event1.port, event2.port);
ports = await navigator.serial.getPorts();
assert_equals(ports.length, 2);
assert_in_array(event1.port, ports);
assert_in_array(event2.port, ports);
}, 'A "connect" event is fired when ports are added.');
</script>
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
<script src="file:///gen/mojo/public/mojom/base/unguessable_token.mojom.js"></script>
<script src="file:///gen/third_party/blink/public/mojom/serial/serial.mojom.js"></script>
<script src="resources/serial-test-utils.js"></script>
<script>
serial_test(async (t, fake) => {
const eventWatcher =
new EventWatcher(t, navigator.serial, ['connect', 'disconnect']);
// Wait for getPorts() to resolve in order to ensure that the Mojo client
// interface has been configured.
let ports = await navigator.serial.getPorts();
assert_equals(ports.length, 0);
// Add ports one at a time so that we can map tokens to ports.
const token1 = fake.addPort();
const port1 = (await eventWatcher.wait_for(['connect'])).port;
const token2 = fake.addPort();
const port2 = (await eventWatcher.wait_for(['connect'])).port;
fake.removePort(token2);
const event1 = await eventWatcher.wait_for(['disconnect']);
assert_true(event1 instanceof SerialConnectionEvent);
assert_equals(event1.port, port2);
ports = await navigator.serial.getPorts();
assert_equals(ports.length, 1);
assert_equals(ports[0], port1);
fake.removePort(token1);
const event2 = await eventWatcher.wait_for(['disconnect']);
assert_true(event2 instanceof SerialConnectionEvent);
assert_equals(event2.port, port1);
ports = await navigator.serial.getPorts();
assert_equals(ports.length, 0);
}, 'A "disconnect" event is fired when ports are added.');
</script>
...@@ -1248,6 +1248,10 @@ Starting worker: resources/global-interface-listing-worker.js ...@@ -1248,6 +1248,10 @@ Starting worker: resources/global-interface-listing-worker.js
[Worker] method getPorts [Worker] method getPorts
[Worker] setter onconnect [Worker] setter onconnect
[Worker] setter ondisconnect [Worker] setter ondisconnect
[Worker] interface SerialConnectionEvent : Event
[Worker] attribute @@toStringTag
[Worker] getter port
[Worker] method constructor
[Worker] interface SerialPort [Worker] interface SerialPort
[Worker] attribute @@toStringTag [Worker] attribute @@toStringTag
[Worker] getter readable [Worker] getter readable
......
...@@ -7953,6 +7953,10 @@ interface Serial : EventTarget ...@@ -7953,6 +7953,10 @@ interface Serial : EventTarget
method requestPort method requestPort
setter onconnect setter onconnect
setter ondisconnect setter ondisconnect
interface SerialConnectionEvent : Event
attribute @@toStringTag
getter port
method constructor
interface SerialPort interface SerialPort
attribute @@toStringTag attribute @@toStringTag
getter readable getter readable
......
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