910e62b5创建于 1月15日历史提交
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/serial/serial_service.h"

#include <map>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/renderer_host/back_forward_cache_disable.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/serial_chooser.h"
#include "content/public/browser/serial_delegate.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "services/device/public/mojom/serial.mojom.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"

namespace content {

SerialService::SerialService(RenderFrameHost* rfh)
    : DocumentUserData<SerialService>(rfh) {
  DCHECK(render_frame_host().IsFeatureEnabled(
      network::mojom::PermissionsPolicyFeature::kSerial));
  // Serial API is not supported for back-forward cache for now because we
  // don't have support for closing/freezing ports when the frame is added to
  // the back-forward cache, so we mark frames that use this API as disabled
  // for back-forward cache.
  BackForwardCache::DisableForRenderFrameHost(
      &render_frame_host(),
      BackForwardCacheDisable::DisabledReason(
          BackForwardCacheDisable::DisabledReasonId::kSerial));

  watchers_.set_disconnect_handler(base::BindRepeating(
      &SerialService::OnWatcherConnectionError, base::Unretained(this)));

  SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
  if (delegate)
    delegate->AddObserver(&render_frame_host(), this);
}

SerialService::~SerialService() {
  SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
  if (delegate)
    delegate->RemoveObserver(&render_frame_host(), this);

  // The remaining watchers will be closed from this end.
  if (!watchers_.empty())
    DecrementActiveFrameCount();
}

void SerialService::Bind(
    mojo::PendingReceiver<blink::mojom::SerialService> receiver) {
  receivers_.Add(this, std::move(receiver));
}

void SerialService::SetClient(
    mojo::PendingRemote<blink::mojom::SerialServiceClient> client) {
  clients_.Add(std::move(client));
}

void SerialService::GetPorts(GetPortsCallback callback) {
  SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
  if (!delegate) {
    std::move(callback).Run(std::vector<blink::mojom::SerialPortInfoPtr>());
    return;
  }

  delegate->GetPortManager(&render_frame_host())
      ->GetDevices(base::BindOnce(&SerialService::FinishGetPorts,
                                  weak_factory_.GetWeakPtr(),
                                  std::move(callback)));
}

void SerialService::RequestPort(
    std::vector<blink::mojom::SerialPortFilterPtr> filters,
    const std::vector<::device::BluetoothUUID>&
        allowed_bluetooth_service_class_ids,
    RequestPortCallback callback) {
  SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
  if (!delegate) {
    std::move(callback).Run(nullptr);
    return;
  }

  if (!delegate->CanRequestPortPermission(&render_frame_host())) {
    std::move(callback).Run(nullptr);
    return;
  }

  chooser_ = delegate->RunChooser(
      &render_frame_host(), std::move(filters),
      allowed_bluetooth_service_class_ids,
      base::BindOnce(&SerialService::FinishRequestPort,
                     weak_factory_.GetWeakPtr(), std::move(callback)));
}

void SerialService::OpenPort(
    const base::UnguessableToken& token,
    device::mojom::SerialConnectionOptionsPtr options,
    mojo::PendingRemote<device::mojom::SerialPortClient> client,
    OpenPortCallback callback) {
  SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
  if (!delegate) {
    std::move(callback).Run(mojo::NullRemote());
    return;
  }

  const auto* port = delegate->GetPortInfo(&render_frame_host(), token);
  if (!port || !delegate->HasPortPermission(&render_frame_host(), *port)) {
    std::move(callback).Run(mojo::NullRemote());
    return;
  }

  if (watchers_.empty()) {
    auto* web_contents_impl = static_cast<WebContentsImpl*>(
        WebContents::FromRenderFrameHost(&render_frame_host()));
    web_contents_impl->IncrementSerialActiveFrameCount();
  }

  mojo::PendingRemote<device::mojom::SerialPortConnectionWatcher> watcher;
  mojo::ReceiverId receiver_id =
      watchers_.Add(this, watcher.InitWithNewPipeAndPassReceiver());
  watcher_ids_.insert({token, receiver_id});

  delegate->GetPortManager(&render_frame_host())
      ->OpenPort(token, /*use_alternate_path=*/false, std::move(options),
                 std::move(client), std::move(watcher), std::move(callback));
}

void SerialService::ForgetPort(const base::UnguessableToken& token,
                               ForgetPortCallback callback) {
  SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
  if (delegate) {
    delegate->RevokePortPermissionWebInitiated(&render_frame_host(), token);
  }
  std::move(callback).Run();
}

void SerialService::OnPortAdded(const device::mojom::SerialPortInfo& port) {
  // Notify clients that a connect event should be dispatched for an added port.
  //
  // In some cases `port` may be disconnected. Wired serial ports are always
  // connected when first added. Bluetooth serial ports are added in the
  // disconnected state if the underlying Bluetooth device is paired with the
  // system but the system has no open connections to the device.
  //
  // Do not notify clients if `port` is disconnected since it would cause a
  // disconnect event to be dispatched for a port that did not previously
  // receive a 'connect' event and hasn't been returned by getPorts().
  if (port.connected) {
    OnPortConnectedStateChanged(port);
  }
}

void SerialService::OnPortRemoved(const device::mojom::SerialPortInfo& port) {
  OnPortConnectedStateChanged(port);
}

void SerialService::OnPortConnectedStateChanged(
    const device::mojom::SerialPortInfo& port) {
  SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
  if (!delegate || !delegate->HasPortPermission(&render_frame_host(), port)) {
    return;
  }

  for (const auto& client : clients_) {
    client->OnPortConnectedStateChanged(ToBlinkType(port));
  }
}

void SerialService::OnPortManagerConnectionError() {
  // Reflect the loss of the SerialPortManager connection into the renderer
  // in order to force caches to be cleared and connections to be
  // re-established.
  receivers_.Clear();
  clients_.Clear();
  chooser_.reset();

  if (!watchers_.empty()) {
    watchers_.Clear();
    DecrementActiveFrameCount();
  }
}

void SerialService::OnPermissionRevoked(const url::Origin& origin) {
  if (origin != render_frame_host().GetMainFrame()->GetLastCommittedOrigin())
    return;

  SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
  size_t watchers_removed =
      std::erase_if(watcher_ids_, [&](const auto& watcher_entry) {
        const auto* port =
            delegate->GetPortInfo(&render_frame_host(), watcher_entry.first);
        if (port && delegate->HasPortPermission(&render_frame_host(), *port))
          return false;

        watchers_.Remove(watcher_entry.second);
        return true;
      });

  if (watchers_removed > 0 && watchers_.empty())
    DecrementActiveFrameCount();
}

void SerialService::FinishGetPorts(
    GetPortsCallback callback,
    std::vector<device::mojom::SerialPortInfoPtr> ports) {
  std::vector<blink::mojom::SerialPortInfoPtr> result;
  SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
  if (!delegate) {
    std::move(callback).Run(std::move(result));
    return;
  }

  for (const auto& port : ports) {
    if (delegate->HasPortPermission(&render_frame_host(), *port))
      result.push_back(ToBlinkType(*port));
  }

  std::move(callback).Run(std::move(result));
}

void SerialService::FinishRequestPort(RequestPortCallback callback,
                                      device::mojom::SerialPortInfoPtr port) {
  SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
  if (!delegate || !port) {
    std::move(callback).Run(nullptr);
    return;
  }

  std::move(callback).Run(ToBlinkType(*port));
}

void SerialService::OnWatcherConnectionError() {
  if (watchers_.empty())
    DecrementActiveFrameCount();

  // Clean up any associated |watcher_ids_| entries.
  std::erase_if(watcher_ids_, [&](const auto& watcher_entry) {
    return watcher_entry.second == watchers_.current_receiver();
  });
}

void SerialService::DecrementActiveFrameCount() {
  auto* web_contents_impl = static_cast<WebContentsImpl*>(
      WebContents::FromRenderFrameHost(&render_frame_host()));
  web_contents_impl->DecrementSerialActiveFrameCount();
}

blink::mojom::SerialPortInfoPtr SerialService::ToBlinkType(
    const device::mojom::SerialPortInfo& port) {
  auto info = blink::mojom::SerialPortInfo::New();
  std::optional<std::string> persistent_identifier;

  info->has_usb_vendor_id = port.has_vendor_id;
  if (port.has_vendor_id) {
    info->usb_vendor_id = port.vendor_id;
  }
  info->has_usb_product_id = port.has_product_id;
  if (port.has_product_id) {
    info->usb_product_id = port.product_id;
  }
  if (port.bluetooth_service_class_id) {
    info->bluetooth_service_class_id = port.bluetooth_service_class_id;
    // Mac address + service uuid can persistently identify a serial port.
    persistent_identifier = base::UTF16ToUTF8(port.path.LossyDisplayName()) +
                            info->bluetooth_service_class_id->value();
  }
  info->connected = port.connected;
  if (persistent_identifier) {
    auto it = token_map_.find(*persistent_identifier);
    if (it == token_map_.end()) {
      auto result = token_map_.insert({*persistent_identifier, port.token});
      CHECK(result.second);
      it = result.first;
    }
    info->token = it->second;
  } else {
    info->token = port.token;
  }
  return info;
}

DOCUMENT_USER_DATA_KEY_IMPL(SerialService);

}  // namespace content