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

#include "components/content_capture/browser/content_capture_receiver.h"

#include <utility>

#include "base/json/json_writer.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/content_capture/browser/onscreen_content_provider.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"

namespace content_capture {

namespace {

OnscreenContentProvider* GetOnscreenContentProvider(
    content::RenderFrameHost* rfh) {
  if (auto* web_contents = content::WebContents::FromRenderFrameHost(rfh))
    return OnscreenContentProvider::FromWebContents(web_contents);
  return nullptr;
}

std::string ToFaviconTypeString(blink::mojom::FaviconIconType type) {
  if (type == blink::mojom::FaviconIconType::kFavicon)
    return "favicon";
  else if (type == blink::mojom::FaviconIconType::kTouchIcon)
    return "touch icon";
  else if (type == blink::mojom::FaviconIconType::kTouchPrecomposedIcon)
    return "touch precomposed icon";
  return "invalid";
}

}  // namespace

// static
std::string ContentCaptureReceiver::ToJSON(
    const std::vector<blink::mojom::FaviconURLPtr>& candidates) {
  if (candidates.empty())
    return std::string();
  base::Value::List favicon_array;
  for (const auto& favicon_url : candidates) {
    base::Value::Dict favicon;
    favicon.Set("url", favicon_url->icon_url.spec());
    favicon.Set("type", ToFaviconTypeString(favicon_url->icon_type));

    if (!favicon_url->icon_sizes.empty()) {
      base::Value::List sizes;
      for (auto icon_size : favicon_url->icon_sizes) {
        base::Value::Dict size;
        size.Set("width", icon_size.width());
        size.Set("height", icon_size.height());
        sizes.Append(std::move(size));
      }
      favicon.Set("sizes", std::move(sizes));
    }
    favicon_array.Append(std::move(favicon));
  }
  return base::WriteJson(favicon_array).value_or("");
}

ContentCaptureReceiver::ContentCaptureReceiver(content::RenderFrameHost* rfh)
    : rfh_(rfh), id_(GetIdFrom(rfh)) {}

ContentCaptureReceiver::~ContentCaptureReceiver() = default;

int64_t ContentCaptureReceiver::GetIdFrom(content::RenderFrameHost* rfh) {
  return static_cast<int64_t>(rfh->GetProcess()->GetDeprecatedID()) << 32 |
         (rfh->GetRoutingID() & 0xFFFFFFFF);
}

void ContentCaptureReceiver::BindPendingReceiver(
    mojo::PendingAssociatedReceiver<mojom::ContentCaptureReceiver>
        pending_receiver) {
  receiver_.Bind(std::move(pending_receiver));
}

void ContentCaptureReceiver::DidCompleteBatchCaptureContent() {
  auto* provider = GetOnscreenContentProvider(rfh_);
  if (!provider) {
    return;
  }

  ContentCaptureFrame frame(frame_content_capture_data_);
  provider->FlushCaptureContent(this, frame);
}

void ContentCaptureReceiver::DidCaptureContent(const ContentCaptureData& data,
                                               bool first_data) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  auto* provider = GetOnscreenContentProvider(rfh_);
  if (!provider)
    return;

  if (first_data) {
    // The session id of this frame isn't changed for new document navigation,
    // so the previous session should be terminated.
    // The parent frame might be captured after child, we need to check if url
    // is changed, otherwise the child frame's session will be removed.
    if (frame_content_capture_data_.id != 0 &&
        frame_content_capture_data_.url != data.value) {
      RemoveSession();
    }

    frame_content_capture_data_.id = id_;
    // Copies everything except id and children.
    frame_content_capture_data_.url = data.value;
    frame_content_capture_data_.bounds = data.bounds;
    RetrieveFaviconURL();

    has_session_ = true;
  }
  // We can't avoid copy the data here because frame needs to be replaced.
  // Always have frame URL attached, since the ContentCaptureConsumer will
  // be reset once activity is resumed, URL is needed to rebuild session.
  ContentCaptureFrame frame(frame_content_capture_data_);
  frame.children = data.children;
  provider->DidCaptureContent(this, frame);
}

void ContentCaptureReceiver::DidUpdateContent(const ContentCaptureData& data) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  auto* provider = GetOnscreenContentProvider(rfh_);
  if (!provider)
    return;

  // We can't avoid copy the data here because frame needs to be replaced.
  ContentCaptureFrame frame(frame_content_capture_data_);
  frame.children = data.children;
  provider->DidUpdateContent(this, frame);
}

void ContentCaptureReceiver::DidRemoveContent(
    const std::vector<int64_t>& data) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  auto* provider = GetOnscreenContentProvider(rfh_);
  if (!provider)
    return;
  provider->DidRemoveContent(this, data);
}

void ContentCaptureReceiver::StartCapture() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (content_capture_enabled_)
    return;

  if (auto& sender = GetContentCaptureSender()) {
    sender->StartCapture();
    content_capture_enabled_ = true;
  }
}

void ContentCaptureReceiver::StopCapture() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!content_capture_enabled_)
    return;

  if (auto& sender = GetContentCaptureSender()) {
    sender->StopCapture();
    content_capture_enabled_ = false;
  }
}

void ContentCaptureReceiver::RemoveSession() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!has_session_)
    return;

  // TODO(crbug.com/40641263): Find a way to notify of session being removed if
  // rfh isn't available.
  if (auto* provider = GetOnscreenContentProvider(rfh_)) {
    provider->DidRemoveSession(this);
    has_session_ = false;
    // We can't reset the frame_content_capture_data_ here, because it could be
    // used by GetFrameContentCaptureDataLastSeen(), has_session_ is used to
    // check if new session shall be created as needed.
  }

  // Cancel the task if any.
  if (notify_title_update_callback_) {
    notify_title_update_callback_->Cancel();
    notify_title_update_callback_ = nullptr;
    title_update_task_runner_ = nullptr;
  }
  exponential_delay_ = 1;
}

void ContentCaptureReceiver::SetTitle(const std::u16string& title) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  frame_content_capture_data_.title = title;
  if (!has_session_)
    return;

  // Returns if there is the pending task.
  if (notify_title_update_callback_)
    return;

  notify_title_update_callback_ =
      std::make_unique<base::CancelableOnceClosure>(base::BindOnce(
          &ContentCaptureReceiver::NotifyTitleUpdate, base::Unretained(this)));

  if (!title_update_task_runner_)
    title_update_task_runner_ = content::GetUIThreadTaskRunner({});

  title_update_task_runner_->PostDelayedTask(
      FROM_HERE, notify_title_update_callback_->callback(),
      base::Seconds(exponential_delay_));

  exponential_delay_ =
      exponential_delay_ < 256 ? exponential_delay_ * 2 : exponential_delay_;
}

void ContentCaptureReceiver::UpdateFaviconURL(
    const std::vector<blink::mojom::FaviconURLPtr>& candidates) {
  if (!has_session_)
    return;
  frame_content_capture_data_.favicon = ToJSON(candidates);
  auto* provider = GetOnscreenContentProvider(rfh_);
  if (!provider)
    return;
  provider->DidUpdateFavicon(this);
}

void ContentCaptureReceiver::RetrieveFaviconURL() {
  if (!rfh()->IsActive() || !rfh()->IsInPrimaryMainFrame() ||
      disable_get_favicon_from_web_contents_for_testing()) {
    frame_content_capture_data_.favicon = std::string();
  } else {
    frame_content_capture_data_.favicon = ToJSON(rfh()->FaviconURLs());
  }
}

void ContentCaptureReceiver::NotifyTitleUpdate() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  if (auto* provider = GetOnscreenContentProvider(rfh_))
    provider->DidUpdateTitle(this);

  // Reset the task after running.
  notify_title_update_callback_ = nullptr;
  title_update_task_runner_ = nullptr;
}

const mojo::AssociatedRemote<mojom::ContentCaptureSender>&
ContentCaptureReceiver::GetContentCaptureSender() {
  if (!content_capture_sender_) {
    rfh_->GetRemoteAssociatedInterfaces()->GetInterface(
        &content_capture_sender_);
  }
  return content_capture_sender_;
}

const ContentCaptureFrame& ContentCaptureReceiver::GetContentCaptureFrame() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  std::u16string url = base::UTF8ToUTF16(rfh_->GetLastCommittedURL().spec());
  if (url == frame_content_capture_data_.url && has_session_)
    return frame_content_capture_data_;

  if (frame_content_capture_data_.id != 0 && has_session_)
    RemoveSession();

  frame_content_capture_data_.id = id_;
  frame_content_capture_data_.url = url;
  const std::optional<gfx::Size>& size = rfh_->GetFrameSize();
  if (size.has_value())
    frame_content_capture_data_.bounds = gfx::Rect(size.value());
  RetrieveFaviconURL();

  has_session_ = true;
  return frame_content_capture_data_;
}

// static
bool
    ContentCaptureReceiver::disable_get_favicon_from_web_contents_for_testing_ =
        false;

void ContentCaptureReceiver::DisableGetFaviconFromWebContentsForTesting() {
  disable_get_favicon_from_web_contents_for_testing_ = true;
}

// static
bool ContentCaptureReceiver::
    disable_get_favicon_from_web_contents_for_testing() {
  return disable_get_favicon_from_web_contents_for_testing_;
}

}  // namespace content_capture