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

#include <stddef.h>

#include <algorithm>
#include <array>
#include <memory>

#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "content/public/browser/context_factory.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_platform_delegate.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/color/color_id.h"
#include "ui/events/event.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/controls/textfield/textfield_controller.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/layout/flex_layout_view.h"
#include "ui/views/test/desktop_test_views_delegate.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "ui/wm/test/wm_test_helper.h"
#else  // !BUILDFLAG(IS_CHROMEOS)
#include "ui/display/screen.h"
#include "ui/views/widget/desktop_aura/desktop_screen.h"
#include "ui/wm/core/wm_state.h"
#endif

#if BUILDFLAG(IS_WIN)
#include <fcntl.h>
#include <io.h>
#endif

namespace content {

struct ShellPlatformDelegate::ShellData {
  gfx::Size content_size;
  // Self-owned Widget, destroyed through CloseNow().
  raw_ptr<views::Widget> window_widget = nullptr;
};

struct ShellPlatformDelegate::PlatformData {
#if BUILDFLAG(IS_CHROMEOS)
  std::unique_ptr<wm::WMTestHelper> wm_test_helper;
#else
  std::unique_ptr<wm::WMState> wm_state;
  std::unique_ptr<display::Screen> screen;
#endif

  // TODO(danakj): This looks unused?
  std::unique_ptr<views::ViewsDelegate> views_delegate;
};

namespace {

// Maintain the UI controls and web view for content shell
class ShellView : public views::BoxLayoutView,
                  public views::TextfieldController {
  METADATA_HEADER(ShellView, views::BoxLayoutView)

 public:
  enum UIControl { BACK_BUTTON, FORWARD_BUTTON, STOP_BUTTON };

  explicit ShellView(Shell* shell) : shell_(shell) { InitShellWindow(); }
  ShellView(const ShellView&) = delete;
  ShellView& operator=(const ShellView&) = delete;
  ~ShellView() override = default;

  // Update the state of UI controls
  void SetAddressBarURL(const GURL& url) {
    url_entry_->SetText(base::ASCIIToUTF16(url.spec()));
  }

  void SetWebContents(WebContents* web_contents, const gfx::Size& size) {
    // If there was a previous WebView in this Shell it should be removed and
    // deleted.
    if (web_view_) {
      // ExtractAsDangling clears the underlying pointer and returns another
      // raw_ptr instance that is allowed to dangle.
      contents_view_->RemoveChildViewT(web_view_.ExtractAsDangling().get());
    }
    views::Builder<views::View>(contents_view_)
        .AddChild(views::Builder<views::WebView>()
                      .CopyAddressTo(&web_view_)
                      .SetBrowserContext(web_contents->GetBrowserContext())
                      .SetWebContents(web_contents)
                      .SetPreferredSize(size))
        .BuildChildren();
    web_contents->Focus();
    web_view_->SizeToPreferredSize();

    // Resize the widget, keeping the same origin.
    gfx::Rect bounds = GetWidget()->GetWindowBoundsInScreen();
    bounds.set_size(GetWidget()->GetRootView()->GetPreferredSize({}));
    GetWidget()->SetBounds(bounds);

    // Resizing a widget on chromeos doesn't automatically resize the root, need
    // to explicitly do that.
#if BUILDFLAG(IS_CHROMEOS)
    GetWidget()->GetNativeWindow()->GetHost()->SetBoundsInPixels(bounds);
#endif
  }

  void EnableUIControl(UIControl control, bool is_enabled) {
    if (control == BACK_BUTTON) {
      back_button_->SetState(is_enabled ? views::Button::STATE_NORMAL
                                        : views::Button::STATE_DISABLED);
    } else if (control == FORWARD_BUTTON) {
      forward_button_->SetState(is_enabled ? views::Button::STATE_NORMAL
                                           : views::Button::STATE_DISABLED);
    } else if (control == STOP_BUTTON) {
      stop_button_->SetState(is_enabled ? views::Button::STATE_NORMAL
                                        : views::Button::STATE_DISABLED);
    }
  }

 private:
  // Initialize the UI control contained in shell window
  void InitShellWindow() {
    auto toolbar_button_rule = [](const views::View* view,
                                  const views::SizeBounds& size_bounds) {
      gfx::Size preferred_size = view->GetPreferredSize({});
      if (size_bounds != views::SizeBounds() &&
          size_bounds.width().is_bounded()) {
        preferred_size.set_width(std::max(
            std::min(size_bounds.width().value(), preferred_size.width()),
            preferred_size.width() / 2));
      }
      return preferred_size;
    };

    auto builder =
        views::Builder<views::BoxLayoutView>(this)
            .SetBackground(
                views::CreateSolidBackground(ui::kColorWindowBackground))
            .SetOrientation(views::BoxLayout::Orientation::kVertical);

    if (!Shell::ShouldHideToolbar()) {
      builder.AddChild(
          views::Builder<views::FlexLayoutView>()
              .CopyAddressTo(&toolbar_view_)
              .SetOrientation(views::LayoutOrientation::kHorizontal)
              // Top padding = 2, Bottom padding = 5
              .SetProperty(views::kMarginsKey, gfx::Insets::TLBR(2, 0, 5, 0))
              .AddChildren(
                  views::Builder<views::MdTextButton>()
                      .CopyAddressTo(&back_button_)
                      .SetText(u"Back")
                      .SetCallback(base::BindRepeating(
                          &Shell::GoBackOrForward,
                          base::Unretained(shell_.get()), -1))
                      .SetProperty(views::kFlexBehaviorKey,
                                   views::FlexSpecification(base::BindRepeating(
                                       toolbar_button_rule))),
                  views::Builder<views::MdTextButton>()
                      .CopyAddressTo(&forward_button_)
                      .SetText(u"Forward")
                      .SetCallback(base::BindRepeating(
                          &Shell::GoBackOrForward,
                          base::Unretained(shell_.get()), 1))
                      .SetProperty(views::kFlexBehaviorKey,
                                   views::FlexSpecification(base::BindRepeating(
                                       toolbar_button_rule))),
                  views::Builder<views::MdTextButton>()
                      .CopyAddressTo(&refresh_button_)
                      .SetText(u"Refresh")
                      .SetCallback(base::BindRepeating(
                          &Shell::Reload, base::Unretained(shell_.get())))
                      .SetProperty(views::kFlexBehaviorKey,
                                   views::FlexSpecification(base::BindRepeating(
                                       toolbar_button_rule))),
                  views::Builder<views::MdTextButton>()
                      .CopyAddressTo(&stop_button_)
                      .SetText(u"Stop")
                      .SetCallback(base::BindRepeating(
                          &Shell::Stop, base::Unretained(shell_.get())))
                      .SetProperty(views::kFlexBehaviorKey,
                                   views::FlexSpecification(base::BindRepeating(
                                       toolbar_button_rule))),
                  views::Builder<views::Textfield>()
                      .CopyAddressTo(&url_entry_)
                      .SetAccessibleName(u"Enter URL")
                      .SetController(this)
                      .SetTextInputType(ui::TextInputType::TEXT_INPUT_TYPE_URL)
                      .SetProperty(
                          views::kFlexBehaviorKey,
                          views::FlexSpecification(
                              views::LayoutOrientation::kHorizontal,
                              views::MinimumFlexSizeRule::kScaleToMinimum,
                              views::MaximumFlexSizeRule::kUnbounded))
                      // Left padding  = 2, Right padding = 2
                      .SetProperty(views::kMarginsKey,
                                   gfx::Insets::TLBR(0, 2, 0, 2))));
    }

    builder.AddChild(views::Builder<views::View>()
                         .CopyAddressTo(&contents_view_)
                         .SetUseDefaultFillLayout(true)
                         .CustomConfigure(base::BindOnce([](views::View* view) {
                           if (!Shell::ShouldHideToolbar()) {
                             view->SetProperty(views::kMarginsKey,
                                               gfx::Insets::TLBR(0, 2, 0, 2));
                           }
                         })));

    if (!Shell::ShouldHideToolbar()) {
      builder.AddChild(views::Builder<views::View>().SetProperty(
          views::kMarginsKey, gfx::Insets::TLBR(0, 0, 5, 0)));
    }

    std::move(builder).BuildChildren();
    SetFlexForView(contents_view_, 1);
  }
  void InitAccelerators() {
    // This function must be called when part of the widget hierarchy.
    DCHECK(GetWidget());
    static const auto keys = std::to_array<ui::KeyboardCode>({
        ui::VKEY_F5,
        ui::VKEY_BROWSER_BACK,
        ui::VKEY_BROWSER_FORWARD,
    });
    for (size_t i = 0; i < std::size(keys); ++i) {
      GetFocusManager()->RegisterAccelerator(
          ui::Accelerator(keys[i], ui::EF_NONE),
          ui::AcceleratorManager::kNormalPriority, this);
    }
  }
  // Overridden from TextfieldController
  void ContentsChanged(views::Textfield* sender,
                       const std::u16string& new_contents) override {}
  bool HandleKeyEvent(views::Textfield* sender,
                      const ui::KeyEvent& key_event) override {
    if (key_event.type() == ui::EventType::kKeyPressed &&
        sender == url_entry_ && key_event.key_code() == ui::VKEY_RETURN) {
      std::string text = base::UTF16ToUTF8(url_entry_->GetText());
      GURL url(text);
      if (!url.has_scheme()) {
        url = GURL(std::string("http://") + std::string(text));
        url_entry_->SetText(base::ASCIIToUTF16(url.spec()));
      }
      shell_->LoadURL(url);
      return true;
    }
    return false;
  }

  // Overridden from View
  gfx::Size GetMinimumSize() const override {
    // We want to be able to make the window smaller than its initial
    // (preferred) size.
    return gfx::Size();
  }
  void AddedToWidget() override { InitAccelerators(); }

  // Overridden from AcceleratorTarget:
  bool AcceleratorPressed(const ui::Accelerator& accelerator) override {
    switch (accelerator.key_code()) {
      case ui::VKEY_F5:
        shell_->Reload();
        return true;
      case ui::VKEY_BROWSER_BACK:
        shell_->GoBackOrForward(-1);
        return true;
      case ui::VKEY_BROWSER_FORWARD:
        shell_->GoBackOrForward(1);
        return true;
      default:
        return views::View::AcceleratorPressed(accelerator);
    }
  }

 private:
  std::unique_ptr<Shell> shell_;

  // Window title
  std::u16string title_;

  // Toolbar view contains forward/backward/reload button and URL entry
  raw_ptr<views::View> toolbar_view_ = nullptr;
  raw_ptr<views::Button> back_button_ = nullptr;
  raw_ptr<views::Button> forward_button_ = nullptr;
  raw_ptr<views::Button> refresh_button_ = nullptr;
  raw_ptr<views::Button> stop_button_ = nullptr;
  raw_ptr<views::Textfield> url_entry_ = nullptr;

  // Contents view contains the web contents view
  raw_ptr<views::View> contents_view_ = nullptr;
  raw_ptr<views::WebView> web_view_ = nullptr;
};

BEGIN_METADATA(ShellView)
END_METADATA

ShellView* ShellViewForWidget(views::Widget* widget) {
  return static_cast<ShellView*>(widget->widget_delegate()->GetContentsView());
}

}  // namespace

ShellPlatformDelegate::ShellPlatformDelegate() = default;

void ShellPlatformDelegate::Initialize(const gfx::Size& default_window_size) {
#if BUILDFLAG(IS_WIN)
  _setmode(_fileno(stdout), _O_BINARY);
  _setmode(_fileno(stderr), _O_BINARY);
#endif

  platform_ = std::make_unique<PlatformData>();

#if BUILDFLAG(IS_CHROMEOS)
  platform_->wm_test_helper =
      std::make_unique<wm::WMTestHelper>(default_window_size);
#else
  platform_->wm_state = std::make_unique<wm::WMState>();
  // FakeScreen tests create their own screen.
  if (!display::Screen::HasScreen())
    platform_->screen = views::CreateDesktopScreen();
#endif

  platform_->views_delegate =
      std::make_unique<views::DesktopTestViewsDelegate>();
}

ShellPlatformDelegate::~ShellPlatformDelegate() = default;

void ShellPlatformDelegate::CreatePlatformWindow(
    Shell* shell,
    const gfx::Size& initial_size) {
  DCHECK(!base::Contains(shell_data_map_, shell));
  ShellData& shell_data = shell_data_map_[shell];

  shell_data.content_size = initial_size;

  auto delegate = std::make_unique<views::WidgetDelegate>();
  delegate->SetContentsView(std::make_unique<ShellView>(shell));
  delegate->SetHasWindowSizeControls(true);
  delegate->SetOwnedByWidget(views::WidgetDelegate::OwnedByWidgetPassKey());

#if BUILDFLAG(IS_CHROMEOS)
  shell_data.window_widget = views::Widget::CreateWindowWithContext(
      std::move(delegate),
      platform_->wm_test_helper->GetDefaultParent(nullptr, gfx::Rect(),
                                                  display::kInvalidDisplayId),
      gfx::Rect(initial_size));
#else
  shell_data.window_widget = new views::Widget();
  views::Widget::InitParams params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET);
  params.bounds = gfx::Rect(initial_size);
  params.delegate = delegate.release();
#if BUILDFLAG(IS_LINUX)
  params.wm_class_class = "chromium-content_shell";
  params.wm_class_name = params.wm_class_class;
#endif  // BUILDFLAG(IS_LINUX)
  shell_data.window_widget->Init(std::move(params));
#endif  // BUILDFLAG(IS_CHROMEOS)

  // |window_widget| is made visible in PlatformSetContents(), so that the
  // platform-window size does not need to change due to layout again.
}

gfx::NativeWindow ShellPlatformDelegate::GetNativeWindow(Shell* shell) {
  DCHECK(base::Contains(shell_data_map_, shell));
  ShellData& shell_data = shell_data_map_[shell];

  return shell_data.window_widget->GetNativeWindow();
}

void ShellPlatformDelegate::CleanUp(Shell* shell) {
  DCHECK(base::Contains(shell_data_map_, shell));
  shell_data_map_.erase(shell);
}

void ShellPlatformDelegate::SetContents(Shell* shell) {
  DCHECK(base::Contains(shell_data_map_, shell));
  ShellData& shell_data = shell_data_map_[shell];

  ShellViewForWidget(shell_data.window_widget)
      ->SetWebContents(shell->web_contents(), shell_data.content_size);
  shell_data.window_widget->GetNativeWindow()->GetHost()->Show();
  shell_data.window_widget->Show();
}

void ShellPlatformDelegate::ResizeWebContent(Shell* shell,
                                             const gfx::Size& content_size) {
  shell->web_contents()->Resize(gfx::Rect(content_size));
}

void ShellPlatformDelegate::EnableUIControl(Shell* shell,
                                            UIControl control,
                                            bool is_enabled) {
  if (Shell::ShouldHideToolbar())
    return;

  DCHECK(base::Contains(shell_data_map_, shell));
  ShellData& shell_data = shell_data_map_[shell];

  auto* view = ShellViewForWidget(shell_data.window_widget);
  if (control == BACK_BUTTON) {
    view->EnableUIControl(ShellView::BACK_BUTTON, is_enabled);
  } else if (control == FORWARD_BUTTON) {
    view->EnableUIControl(ShellView::FORWARD_BUTTON, is_enabled);
  } else if (control == STOP_BUTTON) {
    view->EnableUIControl(ShellView::STOP_BUTTON, is_enabled);
  }
}

void ShellPlatformDelegate::SetAddressBarURL(Shell* shell, const GURL& url) {
  if (Shell::ShouldHideToolbar())
    return;

  DCHECK(base::Contains(shell_data_map_, shell));
  ShellData& shell_data = shell_data_map_[shell];

  ShellViewForWidget(shell_data.window_widget)->SetAddressBarURL(url);
}

void ShellPlatformDelegate::SetIsLoading(Shell* shell, bool loading) {}

void ShellPlatformDelegate::SetTitle(Shell* shell,
                                     const std::u16string& title) {
  DCHECK(base::Contains(shell_data_map_, shell));
  ShellData& shell_data = shell_data_map_[shell];

  shell_data.window_widget->widget_delegate()->SetTitle(title);
}

void ShellPlatformDelegate::MainFrameCreated(Shell* shell,
                                             RenderFrameHost* main_frame) {}

bool ShellPlatformDelegate::DestroyShell(Shell* shell) {
  DCHECK(base::Contains(shell_data_map_, shell));
  ShellData& shell_data = shell_data_map_[shell];

  shell_data.window_widget->CloseNow();
  return true;  // The CloseNow() will do the destruction of Shell.
}

}  // namespace content