// 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 #include #include #include "base/functional/bind.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/api/electron_api_session.h" #include "shell/browser/javascript_environment.h" #include "shell/browser/usb/electron_usb_delegate.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_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, blink::mojom::WebUsbRequestDeviceOptionsPtr options, blink::mojom::WebUsbService::GetPermissionCallback callback, content::WebContents* web_contents, base::WeakPtr usb_delegate) : WebContentsObserver(web_contents), options_(std::move(options)), 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_info=*/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_info=*/nullptr); } else { auto* device_info = chooser_context_->GetDeviceInfo(device_id); if (device_info) { RunCallback(device_info->Clone()); } else { util::EmitWarning( base::StrCat({"The device id ", device_id, " was not found."}), "UnknownUsbDeviceId"); RunCallback(/*device_info=*/nullptr); } } } void UsbChooserController::OnBrowserContextShutdown() { observation_.Reset(); } // Get a list of devices that can be shown in the chooser bubble UI for // user to grant permission. 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 handle_scope{isolate}; // "select-usb-device" should respect |filters|. std::erase_if(devices, [this](const auto& device_info) { return !DisplayDevice(*device_info); }); v8::Local details = gin::DataObjectBuilder(isolate) .Set("deviceList", devices) .Set("frame", rfh) .Build(); prevent_default = session->Emit("select-usb-device", details, base::BindRepeating(&UsbChooserController::OnDeviceChosen, weak_factory_.GetWeakPtr())); } if (!prevent_default) { RunCallback(/*device_info=*/nullptr); } } bool UsbChooserController::DisplayDevice( const device::mojom::UsbDeviceInfo& device_info) const { if (!device::UsbDeviceFilterMatchesAny(options_->filters, device_info)) { return false; } if (std::ranges::any_of( options_->exclusion_filters, [&device_info](const auto& filter) { return device::UsbDeviceFilterMatches(*filter, 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