electron/shell/browser/usb/usb_chooser_controller.cc
John Kleinschmidt 629c54ba36
feat: add support for WebUSB (#36289)
* feat: add support for WebUSB

* fixup for gn check

* fixup gn check on Windows

* Apply review feedback

Co-authored-by: Charles Kerr <charles@charleskerr.com>

* chore: address review feedback

* chore: removed unneeded code

* Migrate non-default ScopedObservation<> instantiations to ScopedObservationTraits<> in chrome/browser/

https://chromium-review.googlesource.com/c/chromium/src/+/4016595

Co-authored-by: Charles Kerr <charles@charleskerr.com>
2022-11-22 16:50:32 -05:00

165 lines
5.6 KiB
C++

// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/usb/usb_chooser_controller.h"
#include <stddef.h>
#include <utility>
#include "base/bind.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "gin/data_object_builder.h"
#include "services/device/public/cpp/usb/usb_utils.h"
#include "services/device/public/mojom/usb_enumeration_options.mojom.h"
#include "shell/browser/javascript_environment.h"
#include "shell/browser/usb/usb_chooser_context_factory.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/content_converter.h"
#include "shell/common/gin_converters/frame_converter.h"
#include "shell/common/gin_converters/usb_device_info_converter.h"
#include "shell/common/node_includes.h"
#include "shell/common/process_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
using content::RenderFrameHost;
using content::WebContents;
namespace electron {
UsbChooserController::UsbChooserController(
RenderFrameHost* render_frame_host,
std::vector<device::mojom::UsbDeviceFilterPtr> device_filters,
blink::mojom::WebUsbService::GetPermissionCallback callback,
content::WebContents* web_contents,
base::WeakPtr<ElectronUsbDelegate> usb_delegate)
: WebContentsObserver(web_contents),
filters_(std::move(device_filters)),
callback_(std::move(callback)),
origin_(render_frame_host->GetMainFrame()->GetLastCommittedOrigin()),
usb_delegate_(usb_delegate),
render_frame_host_id_(render_frame_host->GetGlobalId()) {
chooser_context_ = UsbChooserContextFactory::GetForBrowserContext(
web_contents->GetBrowserContext())
->AsWeakPtr();
DCHECK(chooser_context_);
chooser_context_->GetDevices(base::BindOnce(
&UsbChooserController::GotUsbDeviceList, weak_factory_.GetWeakPtr()));
}
UsbChooserController::~UsbChooserController() {
RunCallback(/*device=*/nullptr);
}
api::Session* UsbChooserController::GetSession() {
if (!web_contents()) {
return nullptr;
}
return api::Session::FromBrowserContext(web_contents()->GetBrowserContext());
}
void UsbChooserController::OnDeviceAdded(
const device::mojom::UsbDeviceInfo& device_info) {
if (DisplayDevice(device_info)) {
api::Session* session = GetSession();
if (session) {
session->Emit("usb-device-added", device_info.Clone(), web_contents());
}
}
}
void UsbChooserController::OnDeviceRemoved(
const device::mojom::UsbDeviceInfo& device_info) {
api::Session* session = GetSession();
if (session) {
session->Emit("usb-device-removed", device_info.Clone(), web_contents());
}
}
void UsbChooserController::OnDeviceChosen(gin::Arguments* args) {
std::string device_id;
if (!args->GetNext(&device_id) || device_id.empty()) {
RunCallback(/*device=*/nullptr);
} else {
auto* device_info = chooser_context_->GetDeviceInfo(device_id);
if (device_info) {
RunCallback(device_info->Clone());
} else {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
node::Environment* env = node::Environment::GetCurrent(isolate);
EmitWarning(env, "The device id " + device_id + " was not found.",
"UnknownUsbDeviceId");
RunCallback(/*device=*/nullptr);
}
}
}
void UsbChooserController::OnBrowserContextShutdown() {
observation_.Reset();
}
// Get a list of devices that can be shown in the chooser bubble UI for
// user to grant permsssion.
void UsbChooserController::GotUsbDeviceList(
std::vector<::device::mojom::UsbDeviceInfoPtr> devices) {
// Listen to UsbChooserContext for OnDeviceAdded/Removed events after the
// enumeration.
if (chooser_context_)
observation_.Observe(chooser_context_.get());
bool prevent_default = false;
api::Session* session = GetSession();
if (session) {
auto* rfh = content::RenderFrameHost::FromID(render_frame_host_id_);
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Object> details = gin::DataObjectBuilder(isolate)
.Set("deviceList", devices)
.Set("frame", rfh)
.Build();
prevent_default =
session->Emit("select-usb-device", details,
base::AdaptCallbackForRepeating(
base::BindOnce(&UsbChooserController::OnDeviceChosen,
weak_factory_.GetWeakPtr())));
}
if (!prevent_default) {
RunCallback(/*port=*/nullptr);
}
}
bool UsbChooserController::DisplayDevice(
const device::mojom::UsbDeviceInfo& device_info) const {
if (!device::UsbDeviceFilterMatchesAny(filters_, device_info))
return false;
return true;
}
void UsbChooserController::RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
if (usb_delegate_) {
usb_delegate_->DeleteControllerForFrame(render_frame_host);
}
}
void UsbChooserController::RunCallback(
device::mojom::UsbDeviceInfoPtr device_info) {
if (callback_) {
if (!chooser_context_ || !device_info) {
std::move(callback_).Run(nullptr);
} else {
chooser_context_->GrantDevicePermission(origin_, *device_info);
std::move(callback_).Run(std::move(device_info));
}
}
}
} // namespace electron