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

#include "headless/lib/browser/headless_web_contents_impl.h"

#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/check_deref.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/headless/console_message_logger/headless_console_message_logger.h"
#include "components/viz/common/frame_sinks/copy_output_result.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_termination_info.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/renderer_preferences_util.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/bindings_policy.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/origin_util.h"
#include "headless/lib/browser/directory_enumerator.h"
#include "headless/lib/browser/headless_browser_context_impl.h"
#include "headless/lib/browser/headless_browser_impl.h"
#include "headless/lib/browser/headless_browser_main_parts.h"
#include "headless/lib/browser/headless_platform_delegate.h"
#include "headless/public/switches.h"
#include "printing/buildflags/buildflags.h"
#include "third_party/blink/public/common/peerconnection/webrtc_ip_handling_policy.h"
#include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
#include "third_party/blink/public/mojom/window_features/window_features.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/compositor.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/switches.h"

#if BUILDFLAG(ENABLE_PRINTING)
#include "components/printing/browser/headless/headless_print_manager.h"
#endif

namespace headless {

namespace features {

// Enables prerendering (Speculation Rules API) in the headless mode. This is
// enabled by default but kept as a kill-switch.
BASE_FEATURE(kPrerender2InHeadlessMode, base::FEATURE_ENABLED_BY_DEFAULT);

}  // namespace features

namespace {

void UpdatePrefsFromSystemSettings(blink::RendererPreferences* prefs) {
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
  content::UpdateFontRendererPreferencesFromSystemSettings(prefs);
#endif

  // The values were copied from chrome/browser/renderer_preferences_util.cc.
#if BUILDFLAG(IS_MAC)
  prefs->focus_ring_color = SkColorSetRGB(0x00, 0x5F, 0xCC);
#else
  prefs->focus_ring_color = SkColorSetRGB(0x10, 0x10, 0x10);
#endif
}

}  // namespace

// static
HeadlessWebContentsImpl* HeadlessWebContentsImpl::From(
    HeadlessWebContents* web_contents) {
  // This downcast is safe because there is only one implementation of
  // HeadlessWebContents.
  return static_cast<HeadlessWebContentsImpl*>(web_contents);
}

// static
HeadlessWebContentsImpl* HeadlessWebContentsImpl::From(
    content::WebContents* contents) {
  if (!contents) {
    return nullptr;
  }
  auto& browser_context = CHECK_DEREF(
      HeadlessBrowserContextImpl::From(contents->GetBrowserContext()));
  return browser_context.GetHeadlessWebContents(contents);
}

class HeadlessWebContentsImpl::Delegate : public content::WebContentsDelegate {
 public:
  explicit Delegate(HeadlessWebContentsImpl* headless_web_contents)
      : headless_web_contents_(headless_web_contents) {}

  Delegate(const Delegate&) = delete;
  Delegate& operator=(const Delegate&) = delete;

  void BeforeUnloadFired(content::WebContents* web_contents,
                         bool proceed,
                         bool* proceed_to_fire_unload) override {
    *proceed_to_fire_unload = proceed;
  }

  void ActivateContents(content::WebContents* contents) override {
    contents->GetPrimaryMainFrame()->GetRenderViewHost()->GetWidget()->Focus();
  }

  void CloseContents(content::WebContents* source) override {
    auto& headless_contents =
        CHECK_DEREF(HeadlessWebContentsImpl::From(source));
    headless_contents.Close();
  }

  content::WebContents* AddNewContents(
      content::WebContents* source,
      std::unique_ptr<content::WebContents> new_contents,
      const GURL& target_url,
      WindowOpenDisposition disposition,
      const blink::mojom::WindowFeatures& window_features,
      bool user_gesture,
      bool* was_blocked) override {
    DCHECK(new_contents->GetBrowserContext() ==
           headless_web_contents_->browser_context());

    std::unique_ptr<HeadlessWebContentsImpl> child_contents =
        HeadlessWebContentsImpl::CreateForChildContents(
            headless_web_contents_, std::move(new_contents));
    HeadlessWebContentsImpl* raw_child_contents = child_contents.get();
    headless_web_contents_->browser_context()->RegisterWebContents(
        std::move(child_contents));

    const gfx::Rect default_bounds(
        headless_web_contents_->browser()->options()->window_size);
    const gfx::Rect bounds = window_features.bounds.IsEmpty()
                                 ? default_bounds
                                 : window_features.bounds;
    raw_child_contents->SetBounds(bounds);
    return raw_child_contents->web_contents();
  }

  content::WebContents* OpenURLFromTab(
      content::WebContents* source,
      const content::OpenURLParams& params,
      base::OnceCallback<void(content::NavigationHandle&)>
          navigation_handle_callback) override {
    DCHECK_EQ(source, headless_web_contents_->web_contents());
    content::WebContents* target = nullptr;
    switch (params.disposition) {
      case WindowOpenDisposition::CURRENT_TAB:
        target = source;
        break;

      case WindowOpenDisposition::NEW_POPUP:
      case WindowOpenDisposition::NEW_WINDOW:
      case WindowOpenDisposition::NEW_BACKGROUND_TAB:
      case WindowOpenDisposition::NEW_FOREGROUND_TAB: {
        HeadlessWebContentsImpl* child_contents = HeadlessWebContentsImpl::From(
            headless_web_contents_->browser_context()
                ->CreateWebContentsBuilder()
                .SetWindowBounds(source->GetContainerBounds())
                .Build());
        target = child_contents->web_contents();
        break;
      }

      // TODO(veluca): add support for other disposition types.
      case WindowOpenDisposition::SINGLETON_TAB:
      case WindowOpenDisposition::OFF_THE_RECORD:
      case WindowOpenDisposition::SAVE_TO_DISK:
      case WindowOpenDisposition::IGNORE_ACTION:
      default:
        return nullptr;
    }

    content::NavigationController::LoadURLParams load_url_params(params);
    load_url_params.force_new_browsing_instance =
        headless_web_contents_->browser()
            ->options()
            ->force_new_browsing_instance;

    base::WeakPtr<content::NavigationHandle> navigation =
        target->GetController().LoadURLWithParams(load_url_params);
    if (navigation_handle_callback && navigation) {
      std::move(navigation_handle_callback).Run(*navigation);
    }
    return target;
  }

  bool IsWebContentsCreationOverridden(
      content::RenderFrameHost* opener,
      content::SiteInstance* source_site_instance,
      content::mojom::WindowContainerType window_container_type,
      const GURL& opener_url,
      const std::string& frame_name,
      const GURL& target_url) override {
    return headless_web_contents_->browser_context()
        ->options()
        ->block_new_web_contents();
  }

  void EnumerateDirectory(content::WebContents* web_contents,
                          scoped_refptr<content::FileSelectListener> listener,
                          const base::FilePath& path) override {
    DirectoryEnumerator::Start(path, std::move(listener));
  }

  content::PictureInPictureResult EnterPictureInPicture(
      content::WebContents* web_contents) override {
    return content::PictureInPictureResult::kSuccess;
  }

  bool IsBackForwardCacheSupported(
      content::WebContents& web_contents) override {
    base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
    return command_line->HasSwitch(switches::kEnableBackForwardCache);
  }

  content::PreloadingEligibility IsPrerender2Supported(
      content::WebContents& web_contents,
      content::PreloadingTriggerType trigger_type) override {
    return base::FeatureList::IsEnabled(features::kPrerender2InHeadlessMode)
               ? content::PreloadingEligibility::kEligible
               : content::PreloadingEligibility::
                     kPreloadingUnsupportedByWebContents;
  }

  void RequestPointerLock(content::WebContents* web_contents,
                          bool user_gesture,
                          bool last_unlocked_by_target) override {
    web_contents->GotResponseToPointerLockRequest(
        blink::mojom::PointerLockResult::kSuccess);
  }

  void EnterFullscreenModeForTab(
      content::RenderFrameHost* requesting_frame,
      const blink::mojom::FullscreenOptions& options) override {
    headless_web_contents_->SetWindowState(HeadlessWindowState::kFullscreen);
  }

  void ExitFullscreenModeForTab(content::WebContents* web_contents) override {
    if (IsFullscreenForTabOrPending(web_contents)) {
      headless_web_contents_->SetWindowState(HeadlessWindowState::kNormal);
    }
  }

  bool IsFullscreenForTabOrPending(
      const content::WebContents* web_contents) override {
    return headless_web_contents_->GetWindowState() ==
           HeadlessWindowState::kFullscreen;
  }

  blink::mojom::DisplayMode GetDisplayMode(
      const content::WebContents* web_contents) override {
    return IsFullscreenForTabOrPending(web_contents)
               ? blink::mojom::DisplayMode::kFullscreen
               : blink::mojom::DisplayMode::kBrowser;
  }

  void SetContentsBounds(content::WebContents* source,
                         const gfx::Rect& bounds) override {
    headless_web_contents_->SetBounds(bounds);
  }

  bool DidAddMessageToConsole(content::WebContents* source,
                              blink::mojom::ConsoleMessageLevel log_level,
                              const std::u16string& message,
                              int32_t line_no,
                              const std::u16string& source_id) override {
    LogConsoleMessage(log_level, message, line_no,
                      /*is_builtin_component=*/false, source_id);
    return true;
  }

 private:
  HeadlessBrowserImpl* browser() { return headless_web_contents_->browser(); }

  raw_ptr<HeadlessWebContentsImpl> headless_web_contents_;  // Not owned.
};

namespace {
constexpr uint64_t kBeginFrameSourceId = viz::BeginFrameArgs::kManualSourceId;
}

class HeadlessWebContentsImpl::PendingFrame final
    : public base::RefCounted<HeadlessWebContentsImpl::PendingFrame> {
 public:
  PendingFrame(uint64_t sequence_number, FrameFinishedCallback callback)
      : sequence_number_(sequence_number), callback_(std::move(callback)) {}

  PendingFrame(const PendingFrame&) = delete;
  PendingFrame& operator=(const PendingFrame&) = delete;

  void OnFrameComplete(const viz::BeginFrameAck& ack) {
    DCHECK_EQ(kBeginFrameSourceId, ack.frame_id.source_id);
    DCHECK_EQ(sequence_number_, ack.frame_id.sequence_number);
    has_damage_ = ack.has_damage;
  }

  void OnReadbackComplete(const viz::CopyOutputBitmapWithMetadata& result) {
    const SkBitmap& bitmap = result.bitmap;
    TRACE_EVENT2(
        "headless", "HeadlessWebContentsImpl::PendingFrame::OnReadbackComplete",
        "sequence_number", sequence_number_, "success", !bitmap.drawsNothing());
    if (bitmap.drawsNothing()) {
      LOG(WARNING) << "Readback from surface failed.";
      return;
    }
    bitmap_ = std::make_unique<SkBitmap>(bitmap);
  }

  base::WeakPtr<PendingFrame> AsWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

 private:
  friend class base::RefCounted<PendingFrame>;

  ~PendingFrame() {
    std::move(callback_).Run(has_damage_, std::move(bitmap_), "");
  }

  const uint64_t sequence_number_;

  FrameFinishedCallback callback_;
  bool has_damage_ = false;
  std::unique_ptr<SkBitmap> bitmap_;
  base::WeakPtrFactory<PendingFrame> weak_ptr_factory_{this};
};

// static
std::unique_ptr<HeadlessWebContentsImpl> HeadlessWebContentsImpl::Create(
    HeadlessWebContents::Builder* builder) {
  content::WebContents::CreateParams create_params(builder->browser_context_);
  auto headless_web_contents = base::WrapUnique(
      new HeadlessWebContentsImpl(content::WebContents::Create(create_params)));

  headless_web_contents->begin_frame_control_enabled_ =
      builder->enable_begin_frame_control_ ||
      headless_web_contents->browser()->options()->enable_begin_frame_control;
  headless_web_contents->InitializeWindow(builder->window_bounds_,
                                          builder->window_state_);
  if (!headless_web_contents->OpenURL(builder->initial_url_))
    return nullptr;
  return headless_web_contents;
}

// static
std::unique_ptr<HeadlessWebContentsImpl>
HeadlessWebContentsImpl::CreateForChildContents(
    HeadlessWebContentsImpl* parent,
    std::unique_ptr<content::WebContents> child_contents) {
  auto child =
      base::WrapUnique(new HeadlessWebContentsImpl(std::move(child_contents)));

  // Child contents have their own root window and inherit the BeginFrameControl
  // setting.
  child->begin_frame_control_enabled_ = parent->begin_frame_control_enabled_;
  child->InitializeWindow(child->web_contents_->GetContainerBounds(),
                          HeadlessWindowState::kNormal);
  return child;
}

void HeadlessWebContentsImpl::InitializeWindow(
    const gfx::Rect& bounds,
    HeadlessWindowState window_state) {
  static int window_id = 1;
  window_id_ = window_id++;

  browser()->InitializeWebContents(this);
  SetVisible(/*visible=*/true);
  SetBounds(bounds);
  SetWindowState(window_state);
}

void HeadlessWebContentsImpl::SetVisible(bool visible) {
  headless_window_->SetVisible(visible);
}

void HeadlessWebContentsImpl::SetWindowState(HeadlessWindowState window_state) {
  headless_window_->SetWindowState(window_state);
}

HeadlessWindowState HeadlessWebContentsImpl::GetWindowState() const {
  return headless_window_->window_state();
}

void HeadlessWebContentsImpl::SetBounds(const gfx::Rect& bounds) {
  headless_window_->SetBounds(bounds);
}

HeadlessWebContentsImpl::HeadlessWebContentsImpl(
    std::unique_ptr<content::WebContents> web_contents)
    : web_contents_delegate_(new HeadlessWebContentsImpl::Delegate(this)),
      headless_window_(std::make_unique<HeadlessWindow>(this)),
      web_contents_(std::move(web_contents)) {
#if BUILDFLAG(ENABLE_PRINTING)
  HeadlessPrintManager::CreateForWebContents(web_contents_.get());
#endif
  UpdatePrefsFromSystemSettings(web_contents_->GetMutableRendererPrefs());
  web_contents_->GetMutableRendererPrefs()->accept_languages =
      browser_context()->options()->accept_language();
  web_contents_->GetMutableRendererPrefs()->hinting =
      browser_context()->options()->font_render_hinting();

  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(::switches::kForceWebRtcIPHandlingPolicy)) {
    web_contents_->GetMutableRendererPrefs()->webrtc_ip_handling_policy =
        blink::ToWebRTCIPHandlingPolicy(command_line->GetSwitchValueASCII(
            ::switches::kForceWebRtcIPHandlingPolicy));
  }

  web_contents_->SetDelegate(web_contents_delegate_.get());
}

HeadlessWebContentsImpl::~HeadlessWebContentsImpl() {
  // Defer destruction of WindowTreeHost, as it does sync mojo calls
  // in the destructor of ui::Compositor.
  base::SequencedTaskRunner::GetCurrentDefault()->DeleteSoon(
      FROM_HERE, std::move(window_tree_host_));
}

bool HeadlessWebContentsImpl::OpenURL(const GURL& url) {
  if (!url.is_valid())
    return false;
  content::NavigationController::LoadURLParams params(url);
  params.transition_type = ui::PageTransitionFromInt(
      ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
  params.force_new_browsing_instance =
      browser()->options()->force_new_browsing_instance;

  web_contents_->GetController().LoadURLWithParams(params);
  web_contents_delegate_->ActivateContents(web_contents_.get());
  web_contents_->Focus();
  return true;
}

void HeadlessWebContentsImpl::Close() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  browser_context()->DestroyWebContents(this);
}

content::WebContents* HeadlessWebContentsImpl::web_contents() const {
  return web_contents_.get();
}

HeadlessBrowserImpl* HeadlessWebContentsImpl::browser() const {
  return browser_context()->browser();
}

HeadlessBrowserContextImpl* HeadlessWebContentsImpl::browser_context() const {
  return HeadlessBrowserContextImpl::From(web_contents()->GetBrowserContext());
}

void HeadlessWebContentsImpl::BeginFrame(
    const base::TimeTicks& frame_timeticks,
    const base::TimeTicks& deadline,
    const base::TimeDelta& interval,
    bool animate_only,
    bool capture_screenshot,
    FrameFinishedCallback frame_finished_callback) {
  DCHECK(begin_frame_control_enabled_);
  if (pending_frame_) {
    std::move(frame_finished_callback)
        .Run(false, nullptr, "Another frame is pending");
    return;
  }
  TRACE_EVENT2("headless", "HeadlessWebContentsImpl::BeginFrame", "frame_time",
               frame_timeticks, "capture_screenshot", capture_screenshot);

  int64_t sequence_number = begin_frame_sequence_number_++;
  auto pending_frame = base::MakeRefCounted<PendingFrame>(
      sequence_number, std::move(frame_finished_callback));
  pending_frame_ = pending_frame->AsWeakPtr();
  if (capture_screenshot) {
    content::RenderWidgetHostView* view =
        web_contents()->GetRenderWidgetHostView();
    if (view && view->IsSurfaceAvailableForCopy()) {
      view->CopyFromSurface(
          gfx::Rect(), gfx::Size(),
          base::BindOnce(&PendingFrame::OnReadbackComplete, pending_frame));
    } else {
      LOG(WARNING) << "Surface not ready for screenshot.";
    }
  }

  auto args = viz::BeginFrameArgs::Create(
      BEGINFRAME_FROM_HERE, kBeginFrameSourceId, sequence_number,
      frame_timeticks, deadline, interval, viz::BeginFrameArgs::NORMAL);
  args.animate_only = animate_only;

  ui::Compositor* compositor = browser()->GetCompositor(this);
  CHECK(compositor);
  compositor->IssueExternalBeginFrame(
      args, /*force=*/true,
      base::BindOnce(&PendingFrame::OnFrameComplete, pending_frame));
}

void HeadlessWebContentsImpl::OnVisibilityChanged() {
  headless_window_->visible() ? web_contents_->WasShown()
                              : web_contents_->WasHidden();
}

void HeadlessWebContentsImpl::OnBoundsChanged(const gfx::Rect& old_bounds) {
  const gfx::Rect bounds = headless_window_->bounds();
  browser()->SetWebContentsBounds(this, bounds);
}

void HeadlessWebContentsImpl::OnWindowStateChanged(
    HeadlessWindowState old_window_state) {
  if (headless_window_->window_state() == HeadlessWindowState::kMinimized) {
    SetFocus(/*focus=*/false);
    restore_minimized_window_focus_ = true;
  } else if (restore_minimized_window_focus_) {
    CHECK_EQ(old_window_state, HeadlessWindowState::kMinimized);
    restore_minimized_window_focus_ = false;
    SetFocus(/*focus=*/true);
  }
}

void HeadlessWebContentsImpl::SetFocus(bool focus) {
  if (content::RenderWidgetHost* rwh = web_contents_->GetPrimaryMainFrame()
                                           ->GetRenderViewHost()
                                           ->GetWidget()) {
    if (focus) {
      rwh->Focus();
    } else {
      rwh->Blur();
    }
  }
}

// HeadlessWebContents::Builder ----------------------------------------------

HeadlessWebContents::Builder::Builder(
    HeadlessBrowserContextImpl* browser_context)
    : browser_context_(browser_context),
      window_bounds_(browser_context->options()->window_size()) {}

HeadlessWebContents::Builder::~Builder() = default;

HeadlessWebContents::Builder::Builder(Builder&&) = default;

HeadlessWebContents::Builder& HeadlessWebContents::Builder::SetInitialURL(
    const GURL& initial_url) {
  initial_url_ = initial_url;
  return *this;
}

HeadlessWebContents::Builder& HeadlessWebContents::Builder::SetWindowBounds(
    const gfx::Rect& bounds) {
  window_bounds_ = bounds;
  return *this;
}

HeadlessWebContents::Builder& HeadlessWebContents::Builder::SetWindowState(
    HeadlessWindowState window_state) {
  window_state_ = window_state;
  return *this;
}

HeadlessWebContents::Builder&
HeadlessWebContents::Builder::SetEnableBeginFrameControl(
    bool enable_begin_frame_control) {
  enable_begin_frame_control_ = enable_begin_frame_control;
  return *this;
}

HeadlessWebContents* HeadlessWebContents::Builder::Build() {
  return browser_context_->CreateWebContents(this);
}

}  // namespace headless