Draw PiP 2.0 window frame borders for classic theme on Linux

We draw the borders in a way similar to BrowserFrameViewLinux when
classic theme is enabled on Linux, and still need to handle other
platforms in upcoming CLs. Since there are many codes in common, I'm
adding a utils file to reuse codes.

Bug: 1366897
Change-Id: Id497ee53082fca41ac2c3c7e5eb63ed78d6c8480
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4018006
Commit-Queue: Yiren Wang <yrw@chromium.org>
Reviewed-by: Scott Violet <sky@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1072039}
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index b25ca2e..c2d21e0a 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -5323,6 +5323,8 @@
         "views/frame/browser_frame_view_linux.h",
         "views/frame/browser_frame_view_linux_native.cc",
         "views/frame/browser_frame_view_linux_native.h",
+        "views/frame/browser_frame_view_paint_utils_linux.cc",
+        "views/frame/browser_frame_view_paint_utils_linux.h",
       ]
     }
 
diff --git a/chrome/browser/ui/views/frame/browser_frame_view_layout_linux.cc b/chrome/browser/ui/views/frame/browser_frame_view_layout_linux.cc
index 54508219..7dc44fcc5 100644
--- a/chrome/browser/ui/views/frame/browser_frame_view_layout_linux.cc
+++ b/chrome/browser/ui/views/frame/browser_frame_view_layout_linux.cc
@@ -7,8 +7,7 @@
 #include "base/i18n/rtl.h"
 #include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/views/frame/browser_frame_view_linux.h"
-#include "chrome/browser/ui/views/frame/opaque_browser_frame_view.h"
-#include "ui/gfx/geometry/rect.h"
+#include "chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.h"
 
 namespace {
 
@@ -45,46 +44,10 @@
 }
 
 gfx::Insets BrowserFrameViewLayoutLinux::RestoredFrameBorderInsets() const {
-  if (!delegate_->ShouldDrawRestoredFrameShadow()) {
-    gfx::Insets insets =
-        OpaqueBrowserFrameViewLayout::RestoredFrameBorderInsets();
-    insets.set_top(0);
-    return insets;
-  }
-
-  // The border must be at least as large as the shadow.
-  gfx::Rect frame_extents;
-  const auto tiled_edges = delegate_->GetTiledEdges();
-  for (const auto& shadow_value : view_->GetShadowValues()) {
-    const auto shadow_radius = shadow_value.blur() / 4;
-    const gfx::InsetsF shadow_insets =
-        gfx::InsetsF::TLBR(tiled_edges.top ? 0 : shadow_radius,
-                           tiled_edges.left ? 0 : shadow_radius,
-                           tiled_edges.bottom ? 0 : shadow_radius,
-                           tiled_edges.right ? 0 : shadow_radius);
-    gfx::RectF shadow_extents;
-    shadow_extents.Inset(-shadow_insets);
-    if (!tiled_edges.top) {
-      shadow_extents.set_y(shadow_extents.y() + shadow_value.y());
-      // If the bottom edge is tiled, fix the height to compensate the addition
-      // to the top inset made above.
-      if (tiled_edges.bottom)
-        shadow_extents.set_height(-shadow_extents.y());
-    }
-    frame_extents.Union(gfx::ToEnclosingRect(shadow_extents));
-  }
-
-  // The border must be at least as large as the input region.
-  const auto insets = gfx::Insets::TLBR(tiled_edges.top ? 0 : kResizeBorder,
-                                        tiled_edges.left ? 0 : kResizeBorder,
-                                        tiled_edges.bottom ? 0 : kResizeBorder,
-                                        tiled_edges.right ? 0 : kResizeBorder);
-  gfx::Rect input_extents;
-  input_extents.Inset(-insets);
-  frame_extents.Union(input_extents);
-
-  return gfx::Insets::TLBR(-frame_extents.y(), -frame_extents.x(),
-                           frame_extents.bottom(), frame_extents.right());
+  return GetRestoredFrameBorderInsetsLinux(
+      delegate_->ShouldDrawRestoredFrameShadow(),
+      OpaqueBrowserFrameViewLayout::RestoredFrameBorderInsets(),
+      delegate_->GetTiledEdges(), view_->GetShadowValues(), kResizeBorder);
 }
 
 gfx::Insets BrowserFrameViewLayoutLinux::RestoredFrameEdgeInsets() const {
diff --git a/chrome/browser/ui/views/frame/browser_frame_view_linux.cc b/chrome/browser/ui/views/frame/browser_frame_view_linux.cc
index a06c5f5..257ced2 100644
--- a/chrome/browser/ui/views/frame/browser_frame_view_linux.cc
+++ b/chrome/browser/ui/views/frame/browser_frame_view_linux.cc
@@ -5,16 +5,12 @@
 #include "chrome/browser/ui/views/frame/browser_frame_view_linux.h"
 
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
+#include "chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/desktop_browser_frame_aura_linux.h"
-#include "ui/color/color_id.h"
-#include "ui/color/color_provider.h"
 #include "ui/gfx/geometry/skia_conversions.h"
-#include "ui/gfx/scoped_canvas.h"
-#include "ui/gfx/skia_paint_util.h"
 #include "ui/linux/linux_ui.h"
 #include "ui/views/layout/layout_provider.h"
-#include "ui/views/window/frame_background.h"
 #include "ui/views/window/window_button_order_provider.h"
 
 BrowserFrameViewLinux::BrowserFrameViewLinux(
@@ -79,42 +75,10 @@
 
 void BrowserFrameViewLinux::PaintRestoredFrameBorder(
     gfx::Canvas* canvas) const {
-  auto clip = GetRestoredClipRegion();
-  bool showing_shadow = ShouldDrawRestoredFrameShadow();
-
-  if (auto* frame_bg = frame_background()) {
-    gfx::ScopedCanvas scoped_canvas(canvas);
-    canvas->sk_canvas()->clipRRect(clip, SkClipOp::kIntersect, true);
-    auto border = layout_->MirroredFrameBorderInsets();
-    auto shadow_inset = showing_shadow ? border : gfx::Insets();
-    frame_bg->PaintMaximized(canvas, GetNativeTheme(), GetColorProvider(),
-                             shadow_inset.left(), shadow_inset.top(),
-                             width() - shadow_inset.width());
-    if (!showing_shadow)
-      frame_bg->FillFrameBorders(canvas, this, border.left(), border.right(),
-                                 border.bottom());
-  }
-
-  // If rendering shadows, draw a 1px exterior border, otherwise
-  // draw a 1px interior border.
-  const SkScalar one_pixel = SkFloatToScalar(1 / canvas->image_scale());
-  auto rect = clip;
-  if (showing_shadow)
-    rect.outset(one_pixel, one_pixel);
-  else
-    clip.inset(one_pixel, one_pixel);
-
-  cc::PaintFlags flags;
-  flags.setColor(GetColorProvider()->GetColor(
-      showing_shadow ? ui::kColorBubbleBorderWhenShadowPresent
-                     : ui::kColorBubbleBorder));
-  flags.setAntiAlias(true);
-  if (showing_shadow)
-    flags.setLooper(gfx::CreateShadowDrawLooper(GetShadowValues()));
-
-  gfx::ScopedCanvas scoped_canvas(canvas);
-  canvas->sk_canvas()->clipRRect(clip, SkClipOp::kDifference, true);
-  canvas->sk_canvas()->drawRRect(rect, flags);
+  PaintRestoredFrameBorderLinux(
+      *canvas, *this, frame_background(), GetRestoredClipRegion(),
+      ShouldDrawRestoredFrameShadow(), layout_->MirroredFrameBorderInsets(),
+      GetShadowValues());
 }
 
 void BrowserFrameViewLinux::GetWindowMask(const gfx::Size& size,
diff --git a/chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.cc b/chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.cc
new file mode 100644
index 0000000..0c006d6
--- /dev/null
+++ b/chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.cc
@@ -0,0 +1,101 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.h"
+
+#include "ui/color/color_provider.h"
+#include "ui/gfx/geometry/rect_conversions.h"
+#include "ui/gfx/scoped_canvas.h"
+#include "ui/gfx/skia_paint_util.h"
+#include "ui/views/view.h"
+#include "ui/views/window/frame_background.h"
+
+void PaintRestoredFrameBorderLinux(gfx::Canvas& canvas,
+                                   const views::View& view,
+                                   views::FrameBackground* frame_background,
+                                   const SkRRect& clip,
+                                   bool showing_shadow,
+                                   const gfx::Insets& border,
+                                   const gfx::ShadowValues& shadow_values) {
+  const auto* color_provider = view.GetColorProvider();
+  if (frame_background) {
+    gfx::ScopedCanvas scoped_canvas(&canvas);
+    canvas.sk_canvas()->clipRRect(clip, SkClipOp::kIntersect, true);
+    auto shadow_inset = showing_shadow ? border : gfx::Insets();
+    frame_background->PaintMaximized(
+        &canvas, view.GetNativeTheme(), color_provider, shadow_inset.left(),
+        shadow_inset.top(), view.width() - shadow_inset.width());
+    if (!showing_shadow)
+      frame_background->FillFrameBorders(&canvas, &view, border.left(),
+                                         border.right(), border.bottom());
+  }
+
+  // If rendering shadows, draw a 1px exterior border, otherwise draw a 1px
+  // interior border.
+  const SkScalar one_pixel = SkFloatToScalar(1 / canvas.image_scale());
+  SkRRect outset_rect = clip;
+  SkRRect inset_rect = clip;
+  if (showing_shadow)
+    outset_rect.outset(one_pixel, one_pixel);
+  else
+    inset_rect.inset(one_pixel, one_pixel);
+
+  cc::PaintFlags flags;
+  flags.setColor(color_provider->GetColor(
+      showing_shadow ? ui::kColorBubbleBorderWhenShadowPresent
+                     : ui::kColorBubbleBorder));
+  flags.setAntiAlias(true);
+  if (showing_shadow)
+    flags.setLooper(gfx::CreateShadowDrawLooper(shadow_values));
+
+  gfx::ScopedCanvas scoped_canvas(&canvas);
+  canvas.sk_canvas()->clipRRect(inset_rect, SkClipOp::kDifference, true);
+  canvas.sk_canvas()->drawRRect(outset_rect, flags);
+}
+
+gfx::Insets GetRestoredFrameBorderInsetsLinux(
+    bool showing_shadow,
+    const gfx::Insets& default_border,
+    const ui::WindowTiledEdges& tiled_edges,
+    const gfx::ShadowValues& shadow_values,
+    int resize_border) {
+  if (!showing_shadow) {
+    auto no_shadow_border = default_border;
+    no_shadow_border.set_top(0);
+    return no_shadow_border;
+  }
+
+  // The border must be at least as large as the shadow.
+  gfx::Rect frame_extents;
+  for (const auto& shadow_value : shadow_values) {
+    const auto shadow_radius = shadow_value.blur() / 4;
+    const gfx::InsetsF shadow_insets =
+        gfx::InsetsF::TLBR(tiled_edges.top ? 0 : shadow_radius,
+                           tiled_edges.left ? 0 : shadow_radius,
+                           tiled_edges.bottom ? 0 : shadow_radius,
+                           tiled_edges.right ? 0 : shadow_radius);
+    gfx::RectF shadow_extents;
+    shadow_extents.Inset(-shadow_insets);
+    if (!tiled_edges.top) {
+      shadow_extents.set_y(shadow_extents.y() + shadow_value.y());
+      // If the bottom edge is tiled, fix the height to compensate the addition
+      // to the top inset made above.
+      if (tiled_edges.bottom)
+        shadow_extents.set_height(-shadow_extents.y());
+    }
+    frame_extents.Union(gfx::ToEnclosingRect(shadow_extents));
+  }
+
+  // The border must be at least as large as the input region.
+  const auto insets = gfx::Insets::TLBR(tiled_edges.top ? 0 : resize_border,
+                                        tiled_edges.left ? 0 : resize_border,
+                                        tiled_edges.bottom ? 0 : resize_border,
+                                        tiled_edges.right ? 0 : resize_border);
+  gfx::Rect input_extents;
+  input_extents.Inset(-insets);
+  frame_extents.Union(input_extents);
+
+  return gfx::Insets::TLBR(-frame_extents.y(), -frame_extents.x(),
+                           frame_extents.bottom(), frame_extents.right());
+}
diff --git a/chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.h b/chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.h
new file mode 100644
index 0000000..06e4a47
--- /dev/null
+++ b/chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.h
@@ -0,0 +1,43 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_FRAME_VIEW_PAINT_UTILS_LINUX_H_
+#define CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_FRAME_VIEW_PAINT_UTILS_LINUX_H_
+
+#include "ui/base/ui_base_types.h"
+#include "ui/gfx/shadow_value.h"
+
+class SkRRect;
+
+namespace gfx {
+class Canvas;
+class Insets;
+}  // namespace gfx
+
+namespace views {
+class FrameBackground;
+class View;
+}  // namespace views
+
+// Paint the window borders, shadows and the background of the top bar area for
+// BrowserFrameViewLinux and PictureInPictureBrowserFrameView on Linux.
+void PaintRestoredFrameBorderLinux(gfx::Canvas& canvas,
+                                   const views::View& view,
+                                   views::FrameBackground* frame_background,
+                                   const SkRRect& clip,
+                                   bool showing_shadow,
+                                   const gfx::Insets& border,
+                                   const gfx::ShadowValues& shadow_values);
+
+// Get the insets from the native window edge to the client view when the window
+// is restored for BrowserFrameViewLayoutLinux and
+// PictureInPictureBrowserFrameView on Linux.
+gfx::Insets GetRestoredFrameBorderInsetsLinux(
+    bool showing_shadow,
+    const gfx::Insets& default_border,
+    const ui::WindowTiledEdges& tiled_edges,
+    const gfx::ShadowValues& shadow_values,
+    int resize_border);
+
+#endif  // CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_FRAME_VIEW_PAINT_UTILS_LINUX_H_
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
index ea3a3bab..28493ea 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
@@ -8,6 +8,7 @@
 #include "chrome/browser/ui/browser_content_setting_bubble_model_delegate.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
+#include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/chrome_typography.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/overlay/overlay_window_image_button.h"
@@ -25,6 +26,7 @@
 #include "ui/compositor/layer.h"
 #include "ui/display/screen.h"
 #include "ui/views/widget/widget_delegate.h"
+#include "ui/views/window/frame_background.h"
 #include "ui/views/window/window_shape.h"
 
 #if !BUILDFLAG(IS_MAC)
@@ -33,6 +35,7 @@
 #endif
 
 #if BUILDFLAG(IS_LINUX)
+#include "chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.h"
 #include "chrome/browser/ui/views/frame/desktop_browser_frame_aura_linux.h"
 #endif
 
@@ -47,7 +50,13 @@
 // The height of the controls bar at the top of the window.
 constexpr int kTopControlsHeight = 30;
 
-constexpr int kWindowBorderThickness = 10;
+// Frame border when window shadow is not drawn.
+constexpr int kFrameBorderThickness = 4;
+
+#if BUILDFLAG(IS_LINUX)
+constexpr int kResizeBorder = 10;
+#endif
+
 constexpr int kResizeAreaCornerSize = 16;
 
 // The window has a smaller minimum size than normal Chrome windows.
@@ -150,6 +159,10 @@
                 ->ExitPictureInPicture();
           },
           base::Unretained(this))));
+
+#if BUILDFLAG(IS_LINUX)
+  frame_background_ = std::make_unique<views::FrameBackground>();
+#endif
 }
 
 PictureInPictureBrowserFrameView::~PictureInPictureBrowserFrameView() = default;
@@ -256,17 +269,13 @@
   for (ContentSettingImageView* view : content_setting_views_)
     view->SetIconColor(color_provider->GetColor(kColorOmniboxResultsIcon));
 
-#if BUILDFLAG(IS_LINUX)
-  // If the top bar background is already drawn by window_frame_provider_, skip
-  // drawing it again below.
-  if (window_frame_provider_) {
-    BrowserNonClientFrameView::OnThemeChanged();
-    return;
-  }
-#endif
+#if !BUILDFLAG(IS_LINUX)
+  // On Linux the top bar background will be drawn in OnPaint().
   controls_container_view_->SetBackground(views::CreateSolidBackground(
       SkColorSetA(color_provider->GetColor(kColorPipWindowControlsBackground),
                   SK_AlphaOPAQUE)));
+#endif
+
   BrowserNonClientFrameView::OnThemeChanged();
 }
 
@@ -302,7 +311,7 @@
 }
 
 gfx::Insets PictureInPictureBrowserFrameView::GetInputInsets() const {
-  return gfx::Insets(ShouldDrawFrameShadow() ? -kWindowBorderThickness : 0);
+  return gfx::Insets(ShouldDrawFrameShadow() ? -kResizeBorder : 0);
 }
 
 SkRRect PictureInPictureBrowserFrameView::GetRestoredClipRegion() const {
@@ -315,6 +324,9 @@
   float radius_dip = 0;
   if (window_frame_provider_) {
     radius_dip = window_frame_provider_->GetTopCornerRadiusDip();
+  } else {
+    radius_dip = ChromeLayoutProvider::Get()->GetCornerRadiusMetric(
+        views::Emphasis::kHigh);
   }
   SkVector radii[4]{{radius_dip, radius_dip}, {radius_dip, radius_dip}, {}, {}};
   SkRRect clip;
@@ -497,12 +509,27 @@
 // views::View implementations:
 void PictureInPictureBrowserFrameView::OnPaint(gfx::Canvas* canvas) {
 #if BUILDFLAG(IS_LINUX)
+  // Draw the PiP window frame borders and shadows, including the top bar
+  // background.
   if (window_frame_provider_) {
-    // Draw the PiP window frame borders and shadows, including the top bar
-    // background.
     window_frame_provider_->PaintWindowFrame(
         canvas, GetLocalBounds(), GetTopAreaHeight(), ShouldPaintAsActive(),
         frame()->tiled_edges());
+  } else {
+    DCHECK(frame_background_);
+    frame_background_->set_frame_color(
+        GetFrameColor(BrowserFrameActiveState::kUseCurrent));
+    frame_background_->set_use_custom_frame(frame()->UseCustomFrame());
+    frame_background_->set_is_active(ShouldPaintAsActive());
+    frame_background_->set_theme_image(GetFrameImage());
+    frame_background_->set_theme_image_y_inset(
+        ThemeProperties::kFrameHeightAboveTabs - GetTopAreaHeight());
+    frame_background_->set_theme_overlay_image(GetFrameOverlayImage());
+    frame_background_->set_top_area_height(GetTopAreaHeight());
+    PaintRestoredFrameBorderLinux(
+        *canvas, *this, frame_background_.get(), GetRestoredClipRegion(),
+        ShouldDrawFrameShadow(), MirroredFrameBorderInsets(),
+        GetShadowValues());
   }
 #endif
   BrowserNonClientFrameView::OnPaint(canvas);
@@ -576,8 +603,12 @@
                              tiled_edges.bottom ? 0 : insets.bottom(),
                              tiled_edges.right ? 0 : insets.right());
   }
+  return GetRestoredFrameBorderInsetsLinux(
+      ShouldDrawFrameShadow(), gfx::Insets(kFrameBorderThickness),
+      frame()->tiled_edges(), GetShadowValues(), kResizeBorder);
+#else
+  return gfx::Insets(kFrameBorderThickness);
 #endif
-  return gfx::Insets(kWindowBorderThickness);
 }
 
 int PictureInPictureBrowserFrameView::GetTopAreaHeight() const {
@@ -587,7 +618,11 @@
 #if BUILDFLAG(IS_LINUX)
 void PictureInPictureBrowserFrameView::SetWindowFrameProvider(
     ui::WindowFrameProvider* window_frame_provider) {
+  DCHECK(window_frame_provider);
   window_frame_provider_ = window_frame_provider;
+
+  // Only one of window_frame_provider_ and frame_background_ will be used.
+  frame_background_.reset();
 }
 
 bool PictureInPictureBrowserFrameView::ShouldDrawFrameShadow() const {
@@ -595,6 +630,13 @@
              frame()->native_browser_frame())
       ->ShouldDrawRestoredFrameShadow();
 }
+
+// static
+gfx::ShadowValues PictureInPictureBrowserFrameView::GetShadowValues() {
+  int elevation = ChromeLayoutProvider::Get()->GetShadowElevationMetric(
+      views::Emphasis::kMaximum);
+  return gfx::ShadowValue::MakeMdShadowValues(elevation);
+}
 #endif
 
 BEGIN_METADATA(PictureInPictureBrowserFrameView, BrowserNonClientFrameView)
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
index 92e3eae..9dd02c5 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
@@ -25,6 +25,7 @@
 #endif
 
 namespace views {
+class FrameBackground;
 class Label;
 }
 
@@ -155,6 +156,10 @@
 
   // Returns whether a client-side shadow should be drawn for the window.
   bool ShouldDrawFrameShadow() const;
+
+  // Gets the shadow metrics (radius, offset, and number of shadows) even if
+  // shadows are not drawn.
+  static gfx::ShadowValues GetShadowValues();
 #endif
 
  private:
@@ -185,6 +190,10 @@
   // Used to draw window frame borders and shadow on Linux when GTK theme is
   // enabled.
   raw_ptr<ui::WindowFrameProvider> window_frame_provider_ = nullptr;
+
+  // Used to draw window frame borders and shadow on Linux when classic theme is
+  // enabled.
+  std::unique_ptr<views::FrameBackground> frame_background_;
 #endif
 };