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
|
on the failure you should collect a netlog and inspect the download
|
||||||
request.
|
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
|
### Instance Methods
|
||||||
|
|
||||||
The following methods are available on instances of `Session`:
|
The following methods are available on instances of `Session`:
|
||||||
|
@ -420,7 +490,7 @@ session.fromPartition('some-partition').setPermissionRequestHandler((webContents
|
||||||
|
|
||||||
* `handler` Function<Boolean> | null
|
* `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.
|
* `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
|
* `requestingOrigin` String - The origin URL of the permission check
|
||||||
* `details` Object - Some properties are only available on certain permission types.
|
* `details` Object - Some properties are only available on certain permission types.
|
||||||
* `securityOrigin` String - The security origin of the `media` check.
|
* `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/referrer.md",
|
||||||
"docs/api/structures/scrubber-item.md",
|
"docs/api/structures/scrubber-item.md",
|
||||||
"docs/api/structures/segmented-control-segment.md",
|
"docs/api/structures/segmented-control-segment.md",
|
||||||
|
"docs/api/structures/serial-port.md",
|
||||||
"docs/api/structures/service-worker-info.md",
|
"docs/api/structures/service-worker-info.md",
|
||||||
"docs/api/structures/shared-worker-info.md",
|
"docs/api/structures/shared-worker-info.md",
|
||||||
"docs/api/structures/shortcut-details.md",
|
"docs/api/structures/shortcut-details.md",
|
||||||
|
|
|
@ -299,6 +299,14 @@ filenames = {
|
||||||
"shell/browser/relauncher_linux.cc",
|
"shell/browser/relauncher_linux.cc",
|
||||||
"shell/browser/relauncher_mac.cc",
|
"shell/browser/relauncher_mac.cc",
|
||||||
"shell/browser/relauncher_win.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.cc",
|
||||||
"shell/browser/session_preferences.h",
|
"shell/browser/session_preferences.h",
|
||||||
"shell/browser/special_storage_policy.cc",
|
"shell/browser/special_storage_policy.cc",
|
||||||
|
|
|
@ -86,6 +86,7 @@
|
||||||
#include "shell/browser/notifications/notification_presenter.h"
|
#include "shell/browser/notifications/notification_presenter.h"
|
||||||
#include "shell/browser/notifications/platform_notification_service.h"
|
#include "shell/browser/notifications/platform_notification_service.h"
|
||||||
#include "shell/browser/protocol_registry.h"
|
#include "shell/browser/protocol_registry.h"
|
||||||
|
#include "shell/browser/serial/electron_serial_delegate.h"
|
||||||
#include "shell/browser/session_preferences.h"
|
#include "shell/browser/session_preferences.h"
|
||||||
#include "shell/browser/ui/devtools_manager_delegate.h"
|
#include "shell/browser/ui/devtools_manager_delegate.h"
|
||||||
#include "shell/browser/web_contents_permission_helper.h"
|
#include "shell/browser/web_contents_permission_helper.h"
|
||||||
|
@ -1747,4 +1748,10 @@ ElectronBrowserClient::GetPluginMimeTypesWithExternalHandlers(
|
||||||
return mime_types;
|
return mime_types;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content::SerialDelegate* ElectronBrowserClient::GetSerialDelegate() {
|
||||||
|
if (!serial_delegate_)
|
||||||
|
serial_delegate_ = std::make_unique<ElectronSerialDelegate>();
|
||||||
|
return serial_delegate_.get();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace electron
|
} // namespace electron
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include "content/public/browser/web_contents.h"
|
#include "content/public/browser/web_contents.h"
|
||||||
#include "electron/buildflags/buildflags.h"
|
#include "electron/buildflags/buildflags.h"
|
||||||
#include "net/ssl/client_cert_identity.h"
|
#include "net/ssl/client_cert_identity.h"
|
||||||
|
#include "shell/browser/serial/electron_serial_delegate.h"
|
||||||
|
|
||||||
namespace content {
|
namespace content {
|
||||||
class ClientCertificateDelegate;
|
class ClientCertificateDelegate;
|
||||||
|
@ -82,6 +83,7 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
|
||||||
|
|
||||||
void SetCanUseCustomSiteInstance(bool should_disable);
|
void SetCanUseCustomSiteInstance(bool should_disable);
|
||||||
bool CanUseCustomSiteInstance() override;
|
bool CanUseCustomSiteInstance() override;
|
||||||
|
content::SerialDelegate* GetSerialDelegate() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void RenderProcessWillLaunch(content::RenderProcessHost* host) override;
|
void RenderProcessWillLaunch(content::RenderProcessHost* host) override;
|
||||||
|
@ -335,6 +337,8 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
|
||||||
// ProxyingWebSocket classes.
|
// ProxyingWebSocket classes.
|
||||||
uint64_t next_id_ = 0;
|
uint64_t next_id_ = 0;
|
||||||
|
|
||||||
|
std::unique_ptr<ElectronSerialDelegate> serial_delegate_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(ElectronBrowserClient);
|
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);
|
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)
|
WEB_CONTENTS_USER_DATA_KEY_IMPL(WebContentsPermissionHelper)
|
||||||
|
|
||||||
} // namespace electron
|
} // namespace electron
|
||||||
|
|
|
@ -22,6 +22,7 @@ class WebContentsPermissionHelper
|
||||||
POINTER_LOCK = static_cast<int>(content::PermissionType::NUM) + 1,
|
POINTER_LOCK = static_cast<int>(content::PermissionType::NUM) + 1,
|
||||||
FULLSCREEN,
|
FULLSCREEN,
|
||||||
OPEN_EXTERNAL,
|
OPEN_EXTERNAL,
|
||||||
|
SERIAL
|
||||||
};
|
};
|
||||||
|
|
||||||
// Asynchronous Requests
|
// Asynchronous Requests
|
||||||
|
@ -38,6 +39,7 @@ class WebContentsPermissionHelper
|
||||||
// Synchronous Checks
|
// Synchronous Checks
|
||||||
bool CheckMediaAccessPermission(const GURL& security_origin,
|
bool CheckMediaAccessPermission(const GURL& security_origin,
|
||||||
blink::mojom::MediaStreamType type) const;
|
blink::mojom::MediaStreamType type) const;
|
||||||
|
bool CheckSerialAccessPermission(const url::Origin& embedding_origin) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit WebContentsPermissionHelper(content::WebContents* web_contents);
|
explicit WebContentsPermissionHelper(content::WebContents* web_contents);
|
||||||
|
|
|
@ -199,6 +199,8 @@ v8::Local<v8::Value> Converter<content::PermissionType>::ToV8(
|
||||||
return StringToV8(isolate, "fullscreen");
|
return StringToV8(isolate, "fullscreen");
|
||||||
case PermissionType::OPEN_EXTERNAL:
|
case PermissionType::OPEN_EXTERNAL:
|
||||||
return StringToV8(isolate, "openExternal");
|
return StringToV8(isolate, "openExternal");
|
||||||
|
case PermissionType::SERIAL:
|
||||||
|
return StringToV8(isolate, "serial");
|
||||||
default:
|
default:
|
||||||
return StringToV8(isolate, "unknown");
|
return StringToV8(isolate, "unknown");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1455,3 +1455,51 @@ describe('iframe using HTML fullscreen API while window is OS-fullscreened', ()
|
||||||
expect(width).to.equal(0);
|
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');
|
v8.setFlagsFromString('--expose_gc');
|
||||||
app.commandLine.appendSwitch('js-flags', '--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
|
// Prevent the spec runner quiting when the first window closes
|
||||||
app.on('window-all-closed', () => null);
|
app.on('window-all-closed', () => null);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue