feat: add serial api support (#25237)
* feat: add serial api support resolves #22478 * Put serial port support behind a flag and mark as experimental * Update docs/api/session.md Co-authored-by: Jeremy Rose <jeremya@chromium.org> * Use enable-blink-features=Serial instead of enable-experimental-web-platform-features * Set enableBlinkFeatures on webPreferences instead of commandline Co-authored-by: Jeremy Rose <jeremya@chromium.org>
This commit is contained in:
parent
bed50bb73c
commit
fd63510ca9
19 changed files with 936 additions and 1 deletions
|
@ -178,6 +178,76 @@ Emitted when a hunspell dictionary file download fails. For details
|
|||
on the failure you should collect a netlog and inspect the download
|
||||
request.
|
||||
|
||||
#### Event: 'select-serial-port' _Experimental_
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `portList` [SerialPort[]](structures/serial-port.md)
|
||||
* `webContents` [WebContents](web-contents.md)
|
||||
* `callback` Function
|
||||
* `portId` String
|
||||
|
||||
Emitted when a serial port needs to be selected when a call to
|
||||
`navigator.serial.requestPort` is made. `callback` should be called with
|
||||
`portId` to be selected, passing an empty string to `callback` will
|
||||
cancel the request. Additionally, permissioning on `navigator.serial` can
|
||||
be managed by using [ses.setPermissionCheckHandler(handler)](#sessetpermissioncheckhandlerhandler)
|
||||
with the `serial` permission.
|
||||
|
||||
Because this is an experimental feature it is disabled by default. To enable this feature, you
|
||||
will need to use the `--enable-features=ElectronSerialChooser` command line switch. Additionally
|
||||
because this is an experimental Chromium feature you will need to set `enableBlinkFeatures: 'Serial'`
|
||||
on the `webPreferences` property when opening a BrowserWindow.
|
||||
|
||||
```javascript
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let win = null
|
||||
app.commandLine.appendSwitch('enable-features', 'ElectronSerialChooser')
|
||||
|
||||
app.whenReady().then(() => {
|
||||
win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
enableBlinkFeatures: 'Serial'
|
||||
}
|
||||
})
|
||||
win.webContents.session.on('select-serial-port', (event, portList, callback) => {
|
||||
event.preventDefault()
|
||||
const selectedPort = portList.find((device) => {
|
||||
return device.vendorId === 0x2341 && device.productId === 0x0043
|
||||
})
|
||||
if (!selectedPort) {
|
||||
callback('')
|
||||
} else {
|
||||
callback(result1.portId)
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### Event: 'serial-port-added' _Experimental_
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `port` [SerialPort](structures/serial-port.md)
|
||||
* `webContents` [WebContents](web-contents.md)
|
||||
|
||||
Emitted after `navigator.serial.requestPort` has been called and `select-serial-port` has fired if a new serial port becomes available. For example, this event will fire when a new USB device is plugged in.
|
||||
|
||||
#### Event: 'serial-port-removed' _Experimental_
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `port` [SerialPort](structures/serial-port.md)
|
||||
* `webContents` [WebContents](web-contents.md)
|
||||
|
||||
Emitted after `navigator.serial.requestPort` has been called and `select-serial-port` has fired if a serial port has been removed. For example, this event will fire when a USB device is unplugged.
|
||||
|
||||
### Instance Methods
|
||||
|
||||
The following methods are available on instances of `Session`:
|
||||
|
@ -420,7 +490,7 @@ session.fromPartition('some-partition').setPermissionRequestHandler((webContents
|
|||
|
||||
* `handler` Function<Boolean> | null
|
||||
* `webContents` [WebContents](web-contents.md) - WebContents checking the permission. Please note that if the request comes from a subframe you should use `requestingUrl` to check the request origin.
|
||||
* `permission` String - Enum of 'media'.
|
||||
* `permission` String - Type of permission check. Valid values are `midiSysex`, `notifications`, `geolocation`, `media`,`mediaKeySystem`,`midi`, `pointerLock`, `fullscreen`, `openExternal`, or `serial`.
|
||||
* `requestingOrigin` String - The origin URL of the permission check
|
||||
* `details` Object - Some properties are only available on certain permission types.
|
||||
* `securityOrigin` String - The security origin of the `media` check.
|
||||
|
|
8
docs/api/structures/serial-port.md
Normal file
8
docs/api/structures/serial-port.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# SerialPort Object
|
||||
|
||||
* `portId` String - Unique identifier for the port
|
||||
* `portName` String - Name of the port
|
||||
* `displayName` String - Addtional information for the port
|
||||
* `persistentId` String - This platform-specific identifier, if present, can be used to identify the device across restarts of the application and operating system.
|
||||
* `vendorId` String - Optional USB vendor ID
|
||||
* `productId` String - Optional USB product ID
|
|
@ -115,6 +115,7 @@ auto_filenames = {
|
|||
"docs/api/structures/referrer.md",
|
||||
"docs/api/structures/scrubber-item.md",
|
||||
"docs/api/structures/segmented-control-segment.md",
|
||||
"docs/api/structures/serial-port.md",
|
||||
"docs/api/structures/service-worker-info.md",
|
||||
"docs/api/structures/shared-worker-info.md",
|
||||
"docs/api/structures/shortcut-details.md",
|
||||
|
|
|
@ -299,6 +299,14 @@ filenames = {
|
|||
"shell/browser/relauncher_linux.cc",
|
||||
"shell/browser/relauncher_mac.cc",
|
||||
"shell/browser/relauncher_win.cc",
|
||||
"shell/browser/serial/electron_serial_delegate.cc",
|
||||
"shell/browser/serial/electron_serial_delegate.h",
|
||||
"shell/browser/serial/serial_chooser_context.cc",
|
||||
"shell/browser/serial/serial_chooser_context.h",
|
||||
"shell/browser/serial/serial_chooser_context_factory.cc",
|
||||
"shell/browser/serial/serial_chooser_context_factory.h",
|
||||
"shell/browser/serial/serial_chooser_controller.cc",
|
||||
"shell/browser/serial/serial_chooser_controller.h",
|
||||
"shell/browser/session_preferences.cc",
|
||||
"shell/browser/session_preferences.h",
|
||||
"shell/browser/special_storage_policy.cc",
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
#include "shell/browser/notifications/notification_presenter.h"
|
||||
#include "shell/browser/notifications/platform_notification_service.h"
|
||||
#include "shell/browser/protocol_registry.h"
|
||||
#include "shell/browser/serial/electron_serial_delegate.h"
|
||||
#include "shell/browser/session_preferences.h"
|
||||
#include "shell/browser/ui/devtools_manager_delegate.h"
|
||||
#include "shell/browser/web_contents_permission_helper.h"
|
||||
|
@ -1747,4 +1748,10 @@ ElectronBrowserClient::GetPluginMimeTypesWithExternalHandlers(
|
|||
return mime_types;
|
||||
}
|
||||
|
||||
content::SerialDelegate* ElectronBrowserClient::GetSerialDelegate() {
|
||||
if (!serial_delegate_)
|
||||
serial_delegate_ = std::make_unique<ElectronSerialDelegate>();
|
||||
return serial_delegate_.get();
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "content/public/browser/web_contents.h"
|
||||
#include "electron/buildflags/buildflags.h"
|
||||
#include "net/ssl/client_cert_identity.h"
|
||||
#include "shell/browser/serial/electron_serial_delegate.h"
|
||||
|
||||
namespace content {
|
||||
class ClientCertificateDelegate;
|
||||
|
@ -82,6 +83,7 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
|
|||
|
||||
void SetCanUseCustomSiteInstance(bool should_disable);
|
||||
bool CanUseCustomSiteInstance() override;
|
||||
content::SerialDelegate* GetSerialDelegate() override;
|
||||
|
||||
protected:
|
||||
void RenderProcessWillLaunch(content::RenderProcessHost* host) override;
|
||||
|
@ -335,6 +337,8 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
|
|||
// ProxyingWebSocket classes.
|
||||
uint64_t next_id_ = 0;
|
||||
|
||||
std::unique_ptr<ElectronSerialDelegate> serial_delegate_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ElectronBrowserClient);
|
||||
};
|
||||
|
||||
|
|
121
shell/browser/serial/electron_serial_delegate.cc
Normal file
121
shell/browser/serial/electron_serial_delegate.cc
Normal file
|
@ -0,0 +1,121 @@
|
|||
// Copyright (c) 2020 Microsoft, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/serial/electron_serial_delegate.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/feature_list.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "shell/browser/api/electron_api_web_contents.h"
|
||||
#include "shell/browser/serial/serial_chooser_context.h"
|
||||
#include "shell/browser/serial/serial_chooser_context_factory.h"
|
||||
#include "shell/browser/serial/serial_chooser_controller.h"
|
||||
#include "shell/browser/web_contents_permission_helper.h"
|
||||
|
||||
namespace features {
|
||||
|
||||
const base::Feature kElectronSerialChooser{"ElectronSerialChooser",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT};
|
||||
}
|
||||
|
||||
namespace electron {
|
||||
|
||||
SerialChooserContext* GetChooserContext(content::RenderFrameHost* frame) {
|
||||
auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
|
||||
auto* browser_context = web_contents->GetBrowserContext();
|
||||
return SerialChooserContextFactory::GetForBrowserContext(browser_context);
|
||||
}
|
||||
|
||||
ElectronSerialDelegate::ElectronSerialDelegate() = default;
|
||||
|
||||
ElectronSerialDelegate::~ElectronSerialDelegate() = default;
|
||||
|
||||
std::unique_ptr<content::SerialChooser> ElectronSerialDelegate::RunChooser(
|
||||
content::RenderFrameHost* frame,
|
||||
std::vector<blink::mojom::SerialPortFilterPtr> filters,
|
||||
content::SerialChooser::Callback callback) {
|
||||
if (base::FeatureList::IsEnabled(features::kElectronSerialChooser)) {
|
||||
SerialChooserController* controller = ControllerForFrame(frame);
|
||||
if (controller) {
|
||||
DeleteControllerForFrame(frame);
|
||||
}
|
||||
AddControllerForFrame(frame, std::move(filters), std::move(callback));
|
||||
} else {
|
||||
// If feature is disabled, immediately return back with no port selected.
|
||||
std::move(callback).Run(nullptr);
|
||||
}
|
||||
|
||||
// Return a nullptr because the return value isn't used for anything, eg
|
||||
// there is no mechanism to cancel navigator.serial.requestPort(). The return
|
||||
// value is simply used in Chromium to cleanup the chooser UI once the serial
|
||||
// service is destroyed.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ElectronSerialDelegate::CanRequestPortPermission(
|
||||
content::RenderFrameHost* frame) {
|
||||
auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
|
||||
auto* permission_helper =
|
||||
WebContentsPermissionHelper::FromWebContents(web_contents);
|
||||
return permission_helper->CheckSerialAccessPermission(
|
||||
web_contents->GetMainFrame()->GetLastCommittedOrigin());
|
||||
}
|
||||
|
||||
bool ElectronSerialDelegate::HasPortPermission(
|
||||
content::RenderFrameHost* frame,
|
||||
const device::mojom::SerialPortInfo& port) {
|
||||
auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
|
||||
auto* browser_context = web_contents->GetBrowserContext();
|
||||
auto* chooser_context =
|
||||
SerialChooserContextFactory::GetForBrowserContext(browser_context);
|
||||
return chooser_context->HasPortPermission(
|
||||
frame->GetLastCommittedOrigin(),
|
||||
web_contents->GetMainFrame()->GetLastCommittedOrigin(), port);
|
||||
}
|
||||
|
||||
device::mojom::SerialPortManager* ElectronSerialDelegate::GetPortManager(
|
||||
content::RenderFrameHost* frame) {
|
||||
return GetChooserContext(frame)->GetPortManager();
|
||||
}
|
||||
|
||||
void ElectronSerialDelegate::AddObserver(content::RenderFrameHost* frame,
|
||||
Observer* observer) {
|
||||
return GetChooserContext(frame)->AddPortObserver(observer);
|
||||
}
|
||||
|
||||
void ElectronSerialDelegate::RemoveObserver(content::RenderFrameHost* frame,
|
||||
Observer* observer) {
|
||||
SerialChooserContext* serial_chooser_context = GetChooserContext(frame);
|
||||
if (serial_chooser_context) {
|
||||
return serial_chooser_context->RemovePortObserver(observer);
|
||||
}
|
||||
}
|
||||
|
||||
SerialChooserController* ElectronSerialDelegate::ControllerForFrame(
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
auto mapping = controller_map_.find(render_frame_host);
|
||||
return mapping == controller_map_.end() ? nullptr : mapping->second.get();
|
||||
}
|
||||
|
||||
SerialChooserController* ElectronSerialDelegate::AddControllerForFrame(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
std::vector<blink::mojom::SerialPortFilterPtr> filters,
|
||||
content::SerialChooser::Callback callback) {
|
||||
auto* web_contents =
|
||||
content::WebContents::FromRenderFrameHost(render_frame_host);
|
||||
auto controller = std::make_unique<SerialChooserController>(
|
||||
render_frame_host, std::move(filters), std::move(callback), web_contents,
|
||||
weak_factory_.GetWeakPtr());
|
||||
controller_map_.insert(
|
||||
std::make_pair(render_frame_host, std::move(controller)));
|
||||
return ControllerForFrame(render_frame_host);
|
||||
}
|
||||
|
||||
void ElectronSerialDelegate::DeleteControllerForFrame(
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
controller_map_.erase(render_frame_host);
|
||||
}
|
||||
|
||||
} // namespace electron
|
60
shell/browser/serial/electron_serial_delegate.h
Normal file
60
shell/browser/serial/electron_serial_delegate.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
// Copyright (c) 2020 Microsoft, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SHELL_BROWSER_SERIAL_ELECTRON_SERIAL_DELEGATE_H_
|
||||
#define SHELL_BROWSER_SERIAL_ELECTRON_SERIAL_DELEGATE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "content/public/browser/serial_delegate.h"
|
||||
#include "shell/browser/serial/serial_chooser_controller.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
class SerialChooserController;
|
||||
|
||||
class ElectronSerialDelegate : public content::SerialDelegate {
|
||||
public:
|
||||
ElectronSerialDelegate();
|
||||
~ElectronSerialDelegate() override;
|
||||
|
||||
std::unique_ptr<content::SerialChooser> RunChooser(
|
||||
content::RenderFrameHost* frame,
|
||||
std::vector<blink::mojom::SerialPortFilterPtr> filters,
|
||||
content::SerialChooser::Callback callback) override;
|
||||
bool CanRequestPortPermission(content::RenderFrameHost* frame) override;
|
||||
bool HasPortPermission(content::RenderFrameHost* frame,
|
||||
const device::mojom::SerialPortInfo& port) override;
|
||||
device::mojom::SerialPortManager* GetPortManager(
|
||||
content::RenderFrameHost* frame) override;
|
||||
void AddObserver(content::RenderFrameHost* frame,
|
||||
Observer* observer) override;
|
||||
void RemoveObserver(content::RenderFrameHost* frame,
|
||||
Observer* observer) override;
|
||||
|
||||
void DeleteControllerForFrame(content::RenderFrameHost* render_frame_host);
|
||||
|
||||
private:
|
||||
SerialChooserController* ControllerForFrame(
|
||||
content::RenderFrameHost* render_frame_host);
|
||||
SerialChooserController* AddControllerForFrame(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
std::vector<blink::mojom::SerialPortFilterPtr> filters,
|
||||
content::SerialChooser::Callback callback);
|
||||
|
||||
std::unordered_map<content::RenderFrameHost*,
|
||||
std::unique_ptr<SerialChooserController>>
|
||||
controller_map_;
|
||||
|
||||
base::WeakPtrFactory<ElectronSerialDelegate> weak_factory_{this};
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ElectronSerialDelegate);
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_SERIAL_ELECTRON_SERIAL_DELEGATE_H_
|
162
shell/browser/serial/serial_chooser_context.cc
Normal file
162
shell/browser/serial/serial_chooser_context.cc
Normal file
|
@ -0,0 +1,162 @@
|
|||
// 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 "shell/browser/serial/serial_chooser_context.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/base64.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/values.h"
|
||||
#include "content/public/browser/device_service.h"
|
||||
#include "mojo/public/cpp/bindings/pending_remote.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
constexpr char kPortNameKey[] = "name";
|
||||
constexpr char kPersistentIdKey[] = "persistent_id";
|
||||
constexpr char kTokenKey[] = "token";
|
||||
|
||||
std::string EncodeToken(const base::UnguessableToken& token) {
|
||||
const uint64_t data[2] = {token.GetHighForSerialization(),
|
||||
token.GetLowForSerialization()};
|
||||
std::string buffer;
|
||||
base::Base64Encode(
|
||||
base::StringPiece(reinterpret_cast<const char*>(&data[0]), sizeof(data)),
|
||||
&buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
base::UnguessableToken DecodeToken(base::StringPiece input) {
|
||||
std::string buffer;
|
||||
if (!base::Base64Decode(input, &buffer) ||
|
||||
buffer.length() != sizeof(uint64_t) * 2) {
|
||||
return base::UnguessableToken();
|
||||
}
|
||||
|
||||
const uint64_t* data = reinterpret_cast<const uint64_t*>(buffer.data());
|
||||
return base::UnguessableToken::Deserialize(data[0], data[1]);
|
||||
}
|
||||
|
||||
base::Value PortInfoToValue(const device::mojom::SerialPortInfo& port) {
|
||||
base::Value value(base::Value::Type::DICTIONARY);
|
||||
if (port.display_name && !port.display_name->empty())
|
||||
value.SetStringKey(kPortNameKey, *port.display_name);
|
||||
else
|
||||
value.SetStringKey(kPortNameKey, port.path.LossyDisplayName());
|
||||
if (SerialChooserContext::CanStorePersistentEntry(port))
|
||||
value.SetStringKey(kPersistentIdKey, port.persistent_id.value());
|
||||
else
|
||||
value.SetStringKey(kTokenKey, EncodeToken(port.token));
|
||||
return value;
|
||||
}
|
||||
|
||||
SerialChooserContext::SerialChooserContext() = default;
|
||||
SerialChooserContext::~SerialChooserContext() = default;
|
||||
|
||||
void SerialChooserContext::GrantPortPermission(
|
||||
const url::Origin& requesting_origin,
|
||||
const url::Origin& embedding_origin,
|
||||
const device::mojom::SerialPortInfo& port) {
|
||||
base::Value value = PortInfoToValue(port);
|
||||
port_info_.insert({port.token, value.Clone()});
|
||||
|
||||
ephemeral_ports_[{requesting_origin, embedding_origin}].insert(port.token);
|
||||
}
|
||||
|
||||
bool SerialChooserContext::HasPortPermission(
|
||||
const url::Origin& requesting_origin,
|
||||
const url::Origin& embedding_origin,
|
||||
const device::mojom::SerialPortInfo& port) {
|
||||
auto it = ephemeral_ports_.find({requesting_origin, embedding_origin});
|
||||
if (it != ephemeral_ports_.end()) {
|
||||
const std::set<base::UnguessableToken> ports = it->second;
|
||||
if (base::Contains(ports, port.token))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
bool SerialChooserContext::CanStorePersistentEntry(
|
||||
const device::mojom::SerialPortInfo& port) {
|
||||
// If there is no display name then the path name will be used instead. The
|
||||
// path name is not guaranteed to be stable. For example, on Linux the name
|
||||
// "ttyUSB0" is reused for any USB serial device. A name like that would be
|
||||
// confusing to show in settings when the device is disconnected.
|
||||
if (!port.display_name || port.display_name->empty())
|
||||
return false;
|
||||
|
||||
return port.persistent_id && !port.persistent_id->empty();
|
||||
}
|
||||
|
||||
device::mojom::SerialPortManager* SerialChooserContext::GetPortManager() {
|
||||
EnsurePortManagerConnection();
|
||||
return port_manager_.get();
|
||||
}
|
||||
|
||||
void SerialChooserContext::AddPortObserver(PortObserver* observer) {
|
||||
port_observer_list_.AddObserver(observer);
|
||||
}
|
||||
|
||||
void SerialChooserContext::RemovePortObserver(PortObserver* observer) {
|
||||
port_observer_list_.RemoveObserver(observer);
|
||||
}
|
||||
|
||||
base::WeakPtr<SerialChooserContext> SerialChooserContext::AsWeakPtr() {
|
||||
return weak_factory_.GetWeakPtr();
|
||||
}
|
||||
|
||||
void SerialChooserContext::OnPortAdded(device::mojom::SerialPortInfoPtr port) {
|
||||
for (auto& observer : port_observer_list_)
|
||||
observer.OnPortAdded(*port);
|
||||
}
|
||||
|
||||
void SerialChooserContext::OnPortRemoved(
|
||||
device::mojom::SerialPortInfoPtr port) {
|
||||
for (auto& observer : port_observer_list_)
|
||||
observer.OnPortRemoved(*port);
|
||||
|
||||
std::vector<std::pair<url::Origin, url::Origin>> revoked_url_pairs;
|
||||
for (auto& map_entry : ephemeral_ports_) {
|
||||
std::set<base::UnguessableToken>& ports = map_entry.second;
|
||||
if (ports.erase(port->token) > 0) {
|
||||
revoked_url_pairs.push_back(map_entry.first);
|
||||
}
|
||||
}
|
||||
|
||||
port_info_.erase(port->token);
|
||||
}
|
||||
|
||||
void SerialChooserContext::EnsurePortManagerConnection() {
|
||||
if (port_manager_)
|
||||
return;
|
||||
|
||||
mojo::PendingRemote<device::mojom::SerialPortManager> manager;
|
||||
content::GetDeviceService().BindSerialPortManager(
|
||||
manager.InitWithNewPipeAndPassReceiver());
|
||||
SetUpPortManagerConnection(std::move(manager));
|
||||
}
|
||||
|
||||
void SerialChooserContext::SetUpPortManagerConnection(
|
||||
mojo::PendingRemote<device::mojom::SerialPortManager> manager) {
|
||||
port_manager_.Bind(std::move(manager));
|
||||
port_manager_.set_disconnect_handler(
|
||||
base::BindOnce(&SerialChooserContext::OnPortManagerConnectionError,
|
||||
base::Unretained(this)));
|
||||
|
||||
port_manager_->SetClient(client_receiver_.BindNewPipeAndPassRemote());
|
||||
}
|
||||
|
||||
void SerialChooserContext::OnPortManagerConnectionError() {
|
||||
port_manager_.reset();
|
||||
client_receiver_.reset();
|
||||
|
||||
port_info_.clear();
|
||||
|
||||
ephemeral_ports_.clear();
|
||||
}
|
||||
|
||||
} // namespace electron
|
92
shell/browser/serial/serial_chooser_context.h
Normal file
92
shell/browser/serial/serial_chooser_context.h
Normal file
|
@ -0,0 +1,92 @@
|
|||
// 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.
|
||||
|
||||
#ifndef SHELL_BROWSER_SERIAL_SERIAL_CHOOSER_CONTEXT_H_
|
||||
#define SHELL_BROWSER_SERIAL_SERIAL_CHOOSER_CONTEXT_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/observer_list.h"
|
||||
#include "base/unguessable_token.h"
|
||||
#include "components/keyed_service/core/keyed_service.h"
|
||||
#include "content/public/browser/serial_delegate.h"
|
||||
#include "mojo/public/cpp/bindings/pending_remote.h"
|
||||
#include "mojo/public/cpp/bindings/remote.h"
|
||||
#include "services/device/public/mojom/serial.mojom-forward.h"
|
||||
#include "third_party/blink/public/mojom/serial/serial.mojom.h"
|
||||
#include "url/gurl.h"
|
||||
#include "url/origin.h"
|
||||
|
||||
namespace base {
|
||||
class Value;
|
||||
}
|
||||
|
||||
namespace electron {
|
||||
|
||||
class SerialChooserContext : public KeyedService,
|
||||
public device::mojom::SerialPortManagerClient {
|
||||
public:
|
||||
using PortObserver = content::SerialDelegate::Observer;
|
||||
|
||||
SerialChooserContext();
|
||||
~SerialChooserContext() override;
|
||||
|
||||
// Serial-specific interface for granting and checking permissions.
|
||||
void GrantPortPermission(const url::Origin& requesting_origin,
|
||||
const url::Origin& embedding_origin,
|
||||
const device::mojom::SerialPortInfo& port);
|
||||
bool HasPortPermission(const url::Origin& requesting_origin,
|
||||
const url::Origin& embedding_origin,
|
||||
const device::mojom::SerialPortInfo& port);
|
||||
static bool CanStorePersistentEntry(
|
||||
const device::mojom::SerialPortInfo& port);
|
||||
|
||||
device::mojom::SerialPortManager* GetPortManager();
|
||||
|
||||
void AddPortObserver(PortObserver* observer);
|
||||
void RemovePortObserver(PortObserver* observer);
|
||||
|
||||
base::WeakPtr<SerialChooserContext> AsWeakPtr();
|
||||
|
||||
// SerialPortManagerClient implementation.
|
||||
void OnPortAdded(device::mojom::SerialPortInfoPtr port) override;
|
||||
void OnPortRemoved(device::mojom::SerialPortInfoPtr port) override;
|
||||
|
||||
private:
|
||||
void EnsurePortManagerConnection();
|
||||
void SetUpPortManagerConnection(
|
||||
mojo::PendingRemote<device::mojom::SerialPortManager> manager);
|
||||
void OnPortManagerConnectionError();
|
||||
void OnGetPorts(const url::Origin& requesting_origin,
|
||||
const url::Origin& embedding_origin,
|
||||
blink::mojom::SerialService::GetPortsCallback callback,
|
||||
std::vector<device::mojom::SerialPortInfoPtr> ports);
|
||||
|
||||
// Tracks the set of ports to which an origin (potentially embedded in another
|
||||
// origin) has access to. Key is (requesting_origin, embedding_origin).
|
||||
std::map<std::pair<url::Origin, url::Origin>,
|
||||
std::set<base::UnguessableToken>>
|
||||
ephemeral_ports_;
|
||||
|
||||
// Holds information about ports in |ephemeral_ports_|.
|
||||
std::map<base::UnguessableToken, base::Value> port_info_;
|
||||
|
||||
mojo::Remote<device::mojom::SerialPortManager> port_manager_;
|
||||
mojo::Receiver<device::mojom::SerialPortManagerClient> client_receiver_{this};
|
||||
base::ObserverList<PortObserver> port_observer_list_;
|
||||
|
||||
base::WeakPtrFactory<SerialChooserContext> weak_factory_{this};
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SerialChooserContext);
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_SERIAL_SERIAL_CHOOSER_CONTEXT_H_
|
41
shell/browser/serial/serial_chooser_context_factory.cc
Normal file
41
shell/browser/serial/serial_chooser_context_factory.cc
Normal file
|
@ -0,0 +1,41 @@
|
|||
// 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 "shell/browser/serial/serial_chooser_context_factory.h"
|
||||
|
||||
#include "components/keyed_service/content/browser_context_dependency_manager.h"
|
||||
#include "shell/browser/serial/serial_chooser_context.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
SerialChooserContextFactory::SerialChooserContextFactory()
|
||||
: BrowserContextKeyedServiceFactory(
|
||||
"SerialChooserContext",
|
||||
BrowserContextDependencyManager::GetInstance()) {}
|
||||
|
||||
SerialChooserContextFactory::~SerialChooserContextFactory() {}
|
||||
|
||||
KeyedService* SerialChooserContextFactory::BuildServiceInstanceFor(
|
||||
content::BrowserContext* context) const {
|
||||
return new SerialChooserContext();
|
||||
}
|
||||
|
||||
// static
|
||||
SerialChooserContextFactory* SerialChooserContextFactory::GetInstance() {
|
||||
return base::Singleton<SerialChooserContextFactory>::get();
|
||||
}
|
||||
|
||||
// static
|
||||
SerialChooserContext* SerialChooserContextFactory::GetForBrowserContext(
|
||||
content::BrowserContext* context) {
|
||||
return static_cast<SerialChooserContext*>(
|
||||
GetInstance()->GetServiceForBrowserContext(context, true));
|
||||
}
|
||||
|
||||
content::BrowserContext* SerialChooserContextFactory::GetBrowserContextToUse(
|
||||
content::BrowserContext* context) const {
|
||||
return context;
|
||||
}
|
||||
|
||||
} // namespace electron
|
40
shell/browser/serial/serial_chooser_context_factory.h
Normal file
40
shell/browser/serial/serial_chooser_context_factory.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
// 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.
|
||||
|
||||
#ifndef SHELL_BROWSER_SERIAL_SERIAL_CHOOSER_CONTEXT_FACTORY_H_
|
||||
#define SHELL_BROWSER_SERIAL_SERIAL_CHOOSER_CONTEXT_FACTORY_H_
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/singleton.h"
|
||||
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
|
||||
#include "shell/browser/serial/serial_chooser_context.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
class SerialChooserContext;
|
||||
|
||||
class SerialChooserContextFactory : public BrowserContextKeyedServiceFactory {
|
||||
public:
|
||||
static SerialChooserContext* GetForBrowserContext(
|
||||
content::BrowserContext* context);
|
||||
static SerialChooserContextFactory* GetInstance();
|
||||
|
||||
private:
|
||||
friend struct base::DefaultSingletonTraits<SerialChooserContextFactory>;
|
||||
|
||||
SerialChooserContextFactory();
|
||||
~SerialChooserContextFactory() override;
|
||||
|
||||
// BrowserContextKeyedServiceFactory methods:
|
||||
KeyedService* BuildServiceInstanceFor(
|
||||
content::BrowserContext* context) const override;
|
||||
content::BrowserContext* GetBrowserContextToUse(
|
||||
content::BrowserContext* context) const override;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SerialChooserContextFactory);
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_SERIAL_SERIAL_CHOOSER_CONTEXT_FACTORY_H_
|
181
shell/browser/serial/serial_chooser_controller.cc
Normal file
181
shell/browser/serial/serial_chooser_controller.cc
Normal file
|
@ -0,0 +1,181 @@
|
|||
// Copyright (c) 2020 Microsoft, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/serial/serial_chooser_controller.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "shell/browser/api/electron_api_session.h"
|
||||
#include "shell/browser/serial/serial_chooser_context.h"
|
||||
#include "shell/browser/serial/serial_chooser_context_factory.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_converters/content_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
|
||||
namespace gin {
|
||||
|
||||
template <>
|
||||
struct Converter<device::mojom::SerialPortInfoPtr> {
|
||||
static v8::Local<v8::Value> ToV8(
|
||||
v8::Isolate* isolate,
|
||||
const device::mojom::SerialPortInfoPtr& port) {
|
||||
gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
|
||||
dict.Set("portId", port->token.ToString());
|
||||
dict.Set("portName", port->path.BaseName().LossyDisplayName());
|
||||
if (port->display_name && !port->display_name->empty()) {
|
||||
dict.Set("displayName", *port->display_name);
|
||||
}
|
||||
if (port->persistent_id && !port->persistent_id->empty()) {
|
||||
dict.Set("persistentId", *port->persistent_id);
|
||||
}
|
||||
if (port->has_vendor_id) {
|
||||
dict.Set("vendorId", base::StringPrintf("%u", port->vendor_id));
|
||||
}
|
||||
if (port->has_product_id) {
|
||||
dict.Set("productId", base::StringPrintf("%u", port->product_id));
|
||||
}
|
||||
|
||||
return gin::ConvertToV8(isolate, dict);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace gin
|
||||
|
||||
namespace electron {
|
||||
|
||||
SerialChooserController::SerialChooserController(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
std::vector<blink::mojom::SerialPortFilterPtr> filters,
|
||||
content::SerialChooser::Callback callback,
|
||||
content::WebContents* web_contents,
|
||||
base::WeakPtr<ElectronSerialDelegate> serial_delegate)
|
||||
: WebContentsObserver(web_contents),
|
||||
filters_(std::move(filters)),
|
||||
callback_(std::move(callback)),
|
||||
serial_delegate_(serial_delegate) {
|
||||
requesting_origin_ = render_frame_host->GetLastCommittedOrigin();
|
||||
embedding_origin_ = web_contents->GetMainFrame()->GetLastCommittedOrigin();
|
||||
|
||||
chooser_context_ = SerialChooserContextFactory::GetForBrowserContext(
|
||||
web_contents->GetBrowserContext())
|
||||
->AsWeakPtr();
|
||||
DCHECK(chooser_context_);
|
||||
chooser_context_->GetPortManager()->GetDevices(base::BindOnce(
|
||||
&SerialChooserController::OnGetDevices, weak_factory_.GetWeakPtr()));
|
||||
observer_.Add(chooser_context_.get());
|
||||
}
|
||||
|
||||
SerialChooserController::~SerialChooserController() {
|
||||
RunCallback(/*port=*/nullptr);
|
||||
}
|
||||
|
||||
api::Session* SerialChooserController::GetSession() {
|
||||
if (!web_contents()) {
|
||||
return nullptr;
|
||||
}
|
||||
return api::Session::FromBrowserContext(web_contents()->GetBrowserContext());
|
||||
}
|
||||
|
||||
void SerialChooserController::OnPortAdded(
|
||||
const device::mojom::SerialPortInfo& port) {
|
||||
ports_.push_back(port.Clone());
|
||||
api::Session* session = GetSession();
|
||||
if (session) {
|
||||
session->Emit("serial-port-added", port.Clone(), web_contents());
|
||||
}
|
||||
}
|
||||
|
||||
void SerialChooserController::OnPortRemoved(
|
||||
const device::mojom::SerialPortInfo& port) {
|
||||
const auto it = std::find_if(
|
||||
ports_.begin(), ports_.end(),
|
||||
[&port](const auto& ptr) { return ptr->token == port.token; });
|
||||
if (it != ports_.end()) {
|
||||
api::Session* session = GetSession();
|
||||
if (session) {
|
||||
session->Emit("serial-port-removed", port.Clone(), web_contents());
|
||||
}
|
||||
ports_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void SerialChooserController::OnPortManagerConnectionError() {
|
||||
observer_.RemoveAll();
|
||||
}
|
||||
|
||||
void SerialChooserController::OnDeviceChosen(const std::string& port_id) {
|
||||
if (port_id.empty()) {
|
||||
RunCallback(/*port=*/nullptr);
|
||||
} else {
|
||||
const auto it =
|
||||
std::find_if(ports_.begin(), ports_.end(), [&port_id](const auto& ptr) {
|
||||
return ptr->token.ToString() == port_id;
|
||||
});
|
||||
chooser_context_->GrantPortPermission(requesting_origin_, embedding_origin_,
|
||||
*it->get());
|
||||
RunCallback(it->Clone());
|
||||
}
|
||||
}
|
||||
|
||||
void SerialChooserController::OnGetDevices(
|
||||
std::vector<device::mojom::SerialPortInfoPtr> ports) {
|
||||
// Sort ports by file paths.
|
||||
std::sort(ports.begin(), ports.end(),
|
||||
[](const auto& port1, const auto& port2) {
|
||||
return port1->path.BaseName() < port2->path.BaseName();
|
||||
});
|
||||
|
||||
for (auto& port : ports) {
|
||||
if (FilterMatchesAny(*port))
|
||||
ports_.push_back(std::move(port));
|
||||
}
|
||||
|
||||
bool prevent_default = false;
|
||||
api::Session* session = GetSession();
|
||||
if (session) {
|
||||
prevent_default =
|
||||
session->Emit("select-serial-port", ports_, web_contents(),
|
||||
base::AdaptCallbackForRepeating(base::BindOnce(
|
||||
&SerialChooserController::OnDeviceChosen,
|
||||
weak_factory_.GetWeakPtr())));
|
||||
}
|
||||
if (!prevent_default) {
|
||||
RunCallback(/*port=*/nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool SerialChooserController::FilterMatchesAny(
|
||||
const device::mojom::SerialPortInfo& port) const {
|
||||
if (filters_.empty())
|
||||
return true;
|
||||
|
||||
for (const auto& filter : filters_) {
|
||||
if (filter->has_vendor_id &&
|
||||
(!port.has_vendor_id || filter->vendor_id != port.vendor_id)) {
|
||||
continue;
|
||||
}
|
||||
if (filter->has_product_id &&
|
||||
(!port.has_product_id || filter->product_id != port.product_id)) {
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SerialChooserController::RunCallback(
|
||||
device::mojom::SerialPortInfoPtr port) {
|
||||
if (callback_) {
|
||||
std::move(callback_).Run(std::move(port));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace electron
|
79
shell/browser/serial/serial_chooser_controller.h
Normal file
79
shell/browser/serial/serial_chooser_controller.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
// Copyright (c) 2020 Microsoft, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SHELL_BROWSER_SERIAL_SERIAL_CHOOSER_CONTROLLER_H_
|
||||
#define SHELL_BROWSER_SERIAL_SERIAL_CHOOSER_CONTROLLER_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/scoped_observer.h"
|
||||
#include "base/strings/string16.h"
|
||||
#include "content/public/browser/serial_chooser.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "content/public/browser/web_contents_observer.h"
|
||||
#include "services/device/public/mojom/serial.mojom-forward.h"
|
||||
#include "shell/browser/api/electron_api_session.h"
|
||||
#include "shell/browser/serial/electron_serial_delegate.h"
|
||||
#include "shell/browser/serial/serial_chooser_context.h"
|
||||
#include "third_party/blink/public/mojom/serial/serial.mojom.h"
|
||||
|
||||
namespace content {
|
||||
class RenderFrameHost;
|
||||
} // namespace content
|
||||
|
||||
namespace electron {
|
||||
|
||||
class ElectronSerialDelegate;
|
||||
|
||||
// SerialChooserController provides data for the Serial API permission prompt.
|
||||
class SerialChooserController final : public SerialChooserContext::PortObserver,
|
||||
public content::WebContentsObserver {
|
||||
public:
|
||||
SerialChooserController(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
std::vector<blink::mojom::SerialPortFilterPtr> filters,
|
||||
content::SerialChooser::Callback callback,
|
||||
content::WebContents* web_contents,
|
||||
base::WeakPtr<ElectronSerialDelegate> serial_delegate);
|
||||
~SerialChooserController() override;
|
||||
|
||||
// SerialChooserContext::PortObserver:
|
||||
void OnPortAdded(const device::mojom::SerialPortInfo& port) override;
|
||||
void OnPortRemoved(const device::mojom::SerialPortInfo& port) override;
|
||||
void OnPortManagerConnectionError() override;
|
||||
|
||||
private:
|
||||
api::Session* GetSession();
|
||||
void OnGetDevices(std::vector<device::mojom::SerialPortInfoPtr> ports);
|
||||
bool FilterMatchesAny(const device::mojom::SerialPortInfo& port) const;
|
||||
void RunCallback(device::mojom::SerialPortInfoPtr port);
|
||||
void OnDeviceChosen(const std::string& port_id);
|
||||
|
||||
std::vector<blink::mojom::SerialPortFilterPtr> filters_;
|
||||
content::SerialChooser::Callback callback_;
|
||||
url::Origin requesting_origin_;
|
||||
url::Origin embedding_origin_;
|
||||
|
||||
base::WeakPtr<SerialChooserContext> chooser_context_;
|
||||
ScopedObserver<SerialChooserContext,
|
||||
SerialChooserContext::PortObserver,
|
||||
&SerialChooserContext::AddPortObserver,
|
||||
&SerialChooserContext::RemovePortObserver>
|
||||
observer_{this};
|
||||
|
||||
std::vector<device::mojom::SerialPortInfoPtr> ports_;
|
||||
|
||||
base::WeakPtr<ElectronSerialDelegate> serial_delegate_;
|
||||
|
||||
base::WeakPtrFactory<SerialChooserController> weak_factory_{this};
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SerialChooserController);
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_SERIAL_SERIAL_CHOOSER_CONTROLLER_H_
|
|
@ -160,6 +160,14 @@ bool WebContentsPermissionHelper::CheckMediaAccessPermission(
|
|||
return CheckPermission(content::PermissionType::AUDIO_CAPTURE, &details);
|
||||
}
|
||||
|
||||
bool WebContentsPermissionHelper::CheckSerialAccessPermission(
|
||||
const url::Origin& embedding_origin) const {
|
||||
base::DictionaryValue details;
|
||||
details.SetString("securityOrigin", embedding_origin.GetURL().spec());
|
||||
return CheckPermission(
|
||||
static_cast<content::PermissionType>(PermissionType::SERIAL), &details);
|
||||
}
|
||||
|
||||
WEB_CONTENTS_USER_DATA_KEY_IMPL(WebContentsPermissionHelper)
|
||||
|
||||
} // namespace electron
|
||||
|
|
|
@ -22,6 +22,7 @@ class WebContentsPermissionHelper
|
|||
POINTER_LOCK = static_cast<int>(content::PermissionType::NUM) + 1,
|
||||
FULLSCREEN,
|
||||
OPEN_EXTERNAL,
|
||||
SERIAL
|
||||
};
|
||||
|
||||
// Asynchronous Requests
|
||||
|
@ -38,6 +39,7 @@ class WebContentsPermissionHelper
|
|||
// Synchronous Checks
|
||||
bool CheckMediaAccessPermission(const GURL& security_origin,
|
||||
blink::mojom::MediaStreamType type) const;
|
||||
bool CheckSerialAccessPermission(const url::Origin& embedding_origin) const;
|
||||
|
||||
private:
|
||||
explicit WebContentsPermissionHelper(content::WebContents* web_contents);
|
||||
|
|
|
@ -199,6 +199,8 @@ v8::Local<v8::Value> Converter<content::PermissionType>::ToV8(
|
|||
return StringToV8(isolate, "fullscreen");
|
||||
case PermissionType::OPEN_EXTERNAL:
|
||||
return StringToV8(isolate, "openExternal");
|
||||
case PermissionType::SERIAL:
|
||||
return StringToV8(isolate, "serial");
|
||||
default:
|
||||
return StringToV8(isolate, "unknown");
|
||||
}
|
||||
|
|
|
@ -1455,3 +1455,51 @@ describe('iframe using HTML fullscreen API while window is OS-fullscreened', ()
|
|||
expect(width).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('navigator.serial', () => {
|
||||
let w: BrowserWindow;
|
||||
before(async () => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
enableBlinkFeatures: 'Serial'
|
||||
}
|
||||
});
|
||||
await w.loadFile(path.join(fixturesPath, 'pages', 'blank.html'));
|
||||
});
|
||||
|
||||
const getPorts: any = () => {
|
||||
return w.webContents.executeJavaScript(`
|
||||
navigator.serial.requestPort().then(port => port.toString()).catch(err => err.toString());
|
||||
`, true);
|
||||
};
|
||||
|
||||
after(closeAllWindows);
|
||||
afterEach(() => {
|
||||
session.defaultSession.setPermissionCheckHandler(null);
|
||||
session.defaultSession.removeAllListeners('select-serial-port');
|
||||
});
|
||||
|
||||
it('does not return a port if select-serial-port event is not defined', async () => {
|
||||
w.loadFile(path.join(fixturesPath, 'pages', 'blank.html'));
|
||||
const port = await getPorts();
|
||||
expect(port).to.equal('NotFoundError: No port selected by the user.');
|
||||
});
|
||||
|
||||
it('does not return a port when permission denied', async () => {
|
||||
w.webContents.session.on('select-serial-port', (event, portList, webContents, callback) => {
|
||||
callback(portList[0].portId);
|
||||
});
|
||||
session.defaultSession.setPermissionCheckHandler(() => false);
|
||||
const port = await getPorts();
|
||||
expect(port).to.equal('NotFoundError: No port selected by the user.');
|
||||
});
|
||||
|
||||
it('returns a port when select-serial-port event is defined', async () => {
|
||||
w.webContents.session.on('select-serial-port', (event, portList, webContents, callback) => {
|
||||
callback(portList[0].portId);
|
||||
});
|
||||
const port = await getPorts();
|
||||
expect(port).to.equal('[object SerialPort]');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@ const { app, protocol } = require('electron');
|
|||
|
||||
v8.setFlagsFromString('--expose_gc');
|
||||
app.commandLine.appendSwitch('js-flags', '--expose_gc');
|
||||
app.commandLine.appendSwitch('enable-features', 'ElectronSerialChooser');
|
||||
// Prevent the spec runner quiting when the first window closes
|
||||
app.on('window-all-closed', () => null);
|
||||
|
||||
|
|
Loading…
Reference in a new issue