File: browser_frame_view_win.cc

package info (click to toggle)
chromium 139.0.7258.127-2
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 6,122,156 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (919 lines) | stat: -rw-r--r-- 36,016 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/ui/views/frame/browser_frame_view_win.h"

#include <dwmapi.h>

#include <algorithm>
#include <memory>
#include <utility>

#include "base/trace_event/common/trace_event_common.h"
#include "base/trace_event/trace_event.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/app/chrome_dll_resource.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/frame/browser_caption_button_container_win.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/tab_strip_region_view.h"
#include "chrome/browser/ui/views/frame/webui_tab_strip_container_view.h"
#include "chrome/browser/ui/views/tabs/new_tab_button.h"
#include "chrome/browser/ui/views/tabs/tab.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/browser/web_applications/web_app_icon_manager.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/win/mica_titlebar.h"
#include "chrome/browser/win/titlebar_config.h"
#include "content/public/browser/web_contents.h"
#include "skia/ext/image_operations.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/resource/resource_bundle_win.h"
#include "ui/base/theme_provider.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/win/hwnd_metrics.h"
#include "ui/color/color_provider_key.h"
#include "ui/display/win/dpi.h"
#include "ui/display/win/screen_win.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/dip_util.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/icon_util.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/win/hwnd_util.h"
#include "ui/views/window/client_view.h"

HICON BrowserFrameViewWin::throbber_icons_
    [BrowserFrameViewWin::kThrobberIconCount];

namespace {

// When enabled, a call to BrowserFrame::GetMinimizeButtonOffset() is avoided
// when not needed. Behind a feature to assess impact
// (go/chrome-performance-work-should-be-finched).
// TODO(crbug.com/40897031): Clean up when experiment is complete.
BASE_FEATURE(kAvoidUnnecessaryGetMinimizeButtonOffset,
             "AvoidUnnecessaryGetMinimizeButtonOffset",
             base::FEATURE_DISABLED_BY_DEFAULT);

// Converts the |image| to a Windows icon and returns the corresponding HICON
// handle. |image| is resized to desired |width| and |height| if needed.
base::win::ScopedGDIObject<HICON> CreateHICONFromSkBitmapSizedTo(
    const gfx::ImageSkia& image,
    int width,
    int height) {
  return IconUtil::CreateHICONFromSkBitmap(
      width == image.width() && height == image.height()
          ? *image.bitmap()
          : skia::ImageOperations::Resize(*image.bitmap(),
                                          skia::ImageOperations::RESIZE_BEST,
                                          width, height));
}

// Additional left margin in the title bar when the window is maximized.
// TODO(crbug.com/40890502): Avoid hardcoding sizes like this.
constexpr int kMaximizedLeftMargin = 2;

constexpr int kIconTitleSpacing = 5;

}  // namespace

///////////////////////////////////////////////////////////////////////////////
// BrowserFrameViewWin, public:

BrowserFrameViewWin::BrowserFrameViewWin(BrowserFrame* frame,
                                         BrowserView* browser_view)
    : BrowserNonClientFrameView(frame, browser_view) {
  // We initialize all fields despite some of them being unused in some modes,
  // since it's possible for modes to flip dynamically (e.g. if the user enables
  // a high-contrast theme). Throbber icons are only used when ShowSystemIcon()
  // is true. Everything else here is only used when
  // ShouldBrowserCustomDrawTitlebar() is true.

  if (browser_view->GetSupportsIcon()) {
    InitThrobberIcons();

    AddChildView(views::Builder<TabIconView>()
                     .CopyAddressTo(&window_icon_)
                     .SetModel(this)
                     .SetID(VIEW_ID_WINDOW_ICON)
                     // Stop the icon from intercepting clicks intended for the
                     // HTSYSMENU region of the window. Even though it does
                     // nothing on click, it will still prevent us from giving
                     // the event back to Windows to handle properly.
                     .SetCanProcessEventsWithinSubtree(false)
                     .Build());
  }

  // If this is a web app window, the window title will be part of the
  // BrowserView and thus we don't need to create another one here.
  if (!browser_view->GetIsWebAppType() && browser_view->GetSupportsTitle()) {
    window_title_ = new views::Label(browser_view->GetWindowTitle());
    window_title_->SetSubpixelRenderingEnabled(false);
    window_title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    window_title_->SetID(VIEW_ID_WINDOW_TITLE);
    AddChildViewRaw(window_title_.get());
  }

  caption_button_container_ =
      AddChildView(std::make_unique<BrowserCaptionButtonContainer>(this));
}

BrowserFrameViewWin::~BrowserFrameViewWin() = default;

///////////////////////////////////////////////////////////////////////////////
// BrowserFrameViewWin, BrowserNonClientFrameView implementation:

bool BrowserFrameViewWin::CaptionButtonsOnLeadingEdge() const {
  // Because we don't set WS_EX_LAYOUTRTL (which would conflict with Chrome's
  // own RTL layout logic), Windows always draws the caption buttons on the
  // right, even when we want to be RTL. See crbug.com/560619.
  return !ShouldBrowserCustomDrawTitlebar(browser_view()) &&
         base::i18n::IsRTL();
}

gfx::Rect BrowserFrameViewWin::GetBoundsForTabStripRegion(
    const gfx::Size& tabstrip_minimum_size) const {
  const int x = CaptionButtonsOnLeadingEdge() ? CaptionButtonsRegionWidth() : 0;
  int end_x = width();
  if (!CaptionButtonsOnLeadingEdge()) {
    end_x = std::min(width() - CaptionButtonsRegionWidth(), end_x);
  }
  return gfx::Rect(x, TopAreaHeight(false), std::max(0, end_x - x),
                   tabstrip_minimum_size.height());
}

gfx::Rect BrowserFrameViewWin::GetBoundsForWebAppFrameToolbar(
    const gfx::Size& toolbar_preferred_size) const {
  int x = display::win::GetScreenWin()->GetSystemMetricsInDIP(SM_CXSIZEFRAME);
  if (IsMaximized()) {
    x += kMaximizedLeftMargin;
  }
  if (browser_view()->IsWindowControlsOverlayEnabled()) {
    x = 0;
  } else if (window_icon_) {
    // Add extra padding to the left of the toolbar to account for the window
    // icon.
    x += window_icon_->size().width() + kIconTitleSpacing;
  }

  int trailing_x = width() - CaptionButtonsRegionWidth();
  return gfx::Rect(x, WindowTopY(), std::max(0, trailing_x - x),
                   caption_button_container_->size().height());
}

int BrowserFrameViewWin::GetTopInset(bool restored) const {
  if (browser_view()->GetTabStripVisible() || IsWebUITabStrip()) {
    return TopAreaHeight(restored);
  }
  return ShouldBrowserCustomDrawTitlebar(browser_view())
             ? TitlebarHeight(restored)
             : 0;
}

bool BrowserFrameViewWin::HasVisibleBackgroundTabShapes(
    BrowserFrameActiveState active_state) const {
  DCHECK(GetWidget());

  // Enabling high contrast mode disables the custom-drawn titlebar (so the
  // system-drawn frame will respect the native frame colors) and enables the
  // IncreasedContrastThemeSupplier (which does not respect the native frame
  // colors).
  // TODO(pkasting): https://crbug.com/831769  Change the architecture of the
  // high contrast support to respect system colors, then remove this.
  if (GetNativeTheme()->UserHasContrastPreference()) {
    return true;
  }

  return BrowserNonClientFrameView::HasVisibleBackgroundTabShapes(active_state);
}

SkColor BrowserFrameViewWin::GetCaptionColor(
    BrowserFrameActiveState active_state) const {
  return GetColorProvider()->GetColor(ShouldPaintAsActiveForState(active_state)
                                          ? kColorCaptionForegroundActive
                                          : kColorCaptionForegroundInactive);
}

void BrowserFrameViewWin::UpdateThrobber(bool running) {
  if (ShouldShowWindowIcon(TitlebarType::kCustom)) {
    window_icon_->Update();
  } else if (ShouldShowWindowIcon(TitlebarType::kSystem)) {
    if (throbber_running_) {
      if (running) {
        DisplayNextThrobberFrame();
      } else {
        StopThrobber();
      }
    } else if (running) {
      StartThrobber();
    }
  }
}

gfx::Size BrowserFrameViewWin::GetMinimumSize() const {
  gfx::Size min_size(browser_view()->GetMinimumSize());
  min_size.Enlarge(0, GetTopInset(false));

  gfx::Size titlebar_min_size(
      display::win::GetScreenWin()->GetSystemMetricsInDIP(SM_CXSIZEFRAME) +
          CaptionButtonsRegionWidth(),
      TitlebarHeight(false));
  if (ShouldShowWindowIcon(TitlebarType::kAny)) {
    titlebar_min_size.Enlarge(
        display::win::GetScreenWin()->GetSystemMetricsInDIP(SM_CXSMICON) +
            kIconTitleSpacing,
        0);
  }

  min_size.SetToMax(titlebar_min_size);

  return min_size;
}

void BrowserFrameViewWin::WindowControlsOverlayEnabledChanged() {
  caption_button_container_->OnWindowControlsOverlayEnabledChanged();
}

void BrowserFrameViewWin::PaintAsActiveChanged() {
  BrowserNonClientFrameView::PaintAsActiveChanged();

  // When window controls overlay is enabled, the caption button container is
  // painted to a layer and is not repainted by
  // BrowserNonClientFrameView::PaintAsActiveChanged. Schedule a re-paint here
  // to update the caption button colors.
  if (caption_button_container_->layer()) {
    caption_button_container_->SchedulePaint();
  }
}

///////////////////////////////////////////////////////////////////////////////
// BrowserFrameViewWin, views::NonClientFrameView implementation:

gfx::Rect BrowserFrameViewWin::GetBoundsForClientView() const {
  return client_view_bounds_;
}

gfx::Rect BrowserFrameViewWin::GetWindowBoundsForClientBounds(
    const gfx::Rect& client_bounds) const {
  HWND hwnd = views::HWNDForWidget(frame());
  if (!browser_view()->GetTabStripVisible() && hwnd) {
    // If we don't have a tabstrip, we're either a popup or an app window, in
    // which case we have a standard size non-client area and can just use
    // AdjustWindowRectEx to obtain it. We check for a non-null window handle in
    // case this gets called before the window is actually created.
    RECT rect = client_bounds.ToRECT();
    AdjustWindowRectEx(&rect, GetWindowLong(hwnd, GWL_STYLE), FALSE,
                       GetWindowLong(hwnd, GWL_EXSTYLE));
    return gfx::Rect(rect);
  }

  const int top_inset = GetTopInset(false);
  return gfx::Rect(client_bounds.x(),
                   std::max(0, client_bounds.y() - top_inset),
                   client_bounds.width(), client_bounds.height() + top_inset);
}

int BrowserFrameViewWin::NonClientHitTest(const gfx::Point& point) {
  int super_component = BrowserNonClientFrameView::NonClientHitTest(point);
  if (super_component != HTNOWHERE) {
    return super_component;
  }

  // For app windows and popups without a custom titlebar we haven't customized
  // the frame at all so Windows can figure it out.
  if (!ShouldBrowserCustomDrawTitlebar(browser_view()) &&
      !browser_view()->GetIsNormalType()) {
    return HTNOWHERE;
  }

  // If the point isn't within our bounds, then it's in the native portion of
  // the frame so again Windows can figure it out.
  if (!bounds().Contains(point)) {
    return HTNOWHERE;
  }

  // At the window corners the resize area is not actually bigger, but the 16
  // pixels at the end of the top and bottom edges trigger diagonal resizing.
  constexpr int kResizeCornerWidth = 16;

  const int top_border_thickness = browser_view()->GetIsWebAppType()
                                       ? FrameTopBorderThickness(false)
                                       : GetLayoutConstant(TAB_STRIP_PADDING);

  const int window_component = GetHTComponentForFrame(
      point, gfx::Insets::TLBR(top_border_thickness, 0, 0, 0),
      top_border_thickness, kResizeCornerWidth - FrameBorderThickness(),
      frame()->widget_delegate()->CanResize());

  const int frame_component = frame()->client_view()->NonClientHitTest(point);

  // See if we're in the sysmenu region.  We still have to check the tabstrip
  // first so that clicks in a tab don't get treated as sysmenu clicks.
  if (frame_component != HTCLIENT && ShouldShowWindowIcon(TitlebarType::kAny)) {
    gfx::Rect sys_menu_region(
        0, display::win::GetScreenWin()->GetSystemMetricsInDIP(SM_CYSIZEFRAME),
        display::win::GetScreenWin()->GetSystemMetricsInDIP(SM_CXSMICON),
        display::win::GetScreenWin()->GetSystemMetricsInDIP(SM_CYSMICON));
    if (sys_menu_region.Contains(point)) {
      return HTSYSMENU;
    }
  }

  if (frame_component != HTNOWHERE) {
    // If the clientview  registers a hit within it's bounds, it's still
    // possible that the hit target should be top resize since the tabstrip
    // region paints to the top of the frame. If the frame registered a hit for
    // the Top resize, override the client frame target.
    if (window_component == HTTOP && !IsMaximized()) {
      return window_component;
    }
    return frame_component;
  }

  // Then see if the point is within any of the window controls.
  const gfx::Point local_point =
      ConvertPointToTarget(parent(), caption_button_container_, point);
  if (caption_button_container_->HitTestPoint(local_point)) {
    const int hit_test_result =
        caption_button_container_->NonClientHitTest(local_point);
    if (hit_test_result != HTNOWHERE) {
      return hit_test_result;
    }
  }

  // On Windows, the caption buttons are almost butted up to the top right
  // corner of the window. This code ensures the mouse isn't set to a size
  // cursor while hovering over the caption buttons, thus giving the incorrect
  // impression that the user can resize the window.
  RECT button_bounds = {0};
  if (SUCCEEDED(DwmGetWindowAttribute(views::HWNDForWidget(frame()),
                                      DWMWA_CAPTION_BUTTON_BOUNDS,
                                      &button_bounds, sizeof(button_bounds)))) {
    gfx::RectF button_bounds_in_dips = gfx::ConvertRectToDips(
        gfx::Rect(button_bounds), display::win::GetDPIScale());
    // TODO(crbug.com/40150311): GetMirroredRect() requires an integer rect,
    // but the size in DIPs may not be an integer with a fractional device
    // scale factor. If we want to keep using integers, the choice to use
    // ToFlooredRectDeprecated() seems to be doing the wrong thing given the
    // comment below about insetting 1 DIP instead of 1 physical pixel. We
    // should probably use ToEnclosedRect() and then we could have inset 1
    // physical pixel here.
    gfx::Rect buttons =
        GetMirroredRect(gfx::ToFlooredRectDeprecated(button_bounds_in_dips));

    // There is a small one-pixel strip right above the caption buttons in
    // which the resize border "peeks" through.
    constexpr int kCaptionButtonTopInset = 1;
    // The sizing region at the window edge above the caption buttons is
    // 1 px regardless of scale factor. If we inset by 1 before converting
    // to DIPs, the precision loss might eliminate this region entirely. The
    // best we can do is to inset after conversion. This guarantees we'll
    // show the resize cursor when resizing is possible. The cost of which
    // is also maybe showing it over the portion of the DIP that isn't the
    // outermost pixel.
    buttons.Inset(gfx::Insets::TLBR(kCaptionButtonTopInset, 0, 0, 0));
    if (buttons.Contains(point)) {
      return HTNOWHERE;
    }
  }

  // Fall back to the caption if no other component matches.
  return (window_component == HTNOWHERE) ? HTCAPTION : window_component;
}

void BrowserFrameViewWin::UpdateWindowIcon() {
  if (window_icon_ && window_icon_->GetVisible()) {
    window_icon_->SchedulePaint();
  }
}

void BrowserFrameViewWin::UpdateWindowTitle() {
  LayoutTitleBar();
  if (window_title_ && window_title_->GetVisible()) {
    window_title_->SchedulePaint();
  }
}

void BrowserFrameViewWin::ResetWindowControls() {
  BrowserNonClientFrameView::ResetWindowControls();
  caption_button_container_->ResetWindowControls();
}

void BrowserFrameViewWin::OnThemeChanged() {
  BrowserNonClientFrameView::OnThemeChanged();
  if (!ShouldBrowserCustomDrawTitlebar(browser_view())) {
    SetSystemMicaTitlebarAttributes();
  }
}

bool BrowserFrameViewWin::ShouldTabIconViewAnimate() const {
  if (!ShouldShowWindowIcon(TitlebarType::kCustom)) {
    return false;
  }

  // Web apps use their app icon and shouldn't show a throbber.
  if (browser_view()->GetIsWebAppType()) {
    return false;
  }

  content::WebContents* current_tab = browser_view()->GetActiveWebContents();
  return current_tab && current_tab->ShouldShowLoadingUI();
}

ui::ImageModel BrowserFrameViewWin::GetFaviconForTabIconView() {
  DCHECK(ShouldShowWindowIcon(TitlebarType::kCustom));
  return frame()->widget_delegate()->GetWindowIcon();
}

bool BrowserFrameViewWin::IsMaximized() const {
  return frame()->IsMaximized();
}

bool BrowserFrameViewWin::IsWebUITabStrip() const {
  return WebUITabStripContainerView::UseTouchableTabStrip(
      browser_view()->browser());
}

///////////////////////////////////////////////////////////////////////////////
// BrowserFrameViewWin, views::View overrides:

void BrowserFrameViewWin::OnPaint(gfx::Canvas* canvas) {
  TRACE_EVENT0("views.frame", "BrowserFrameViewWin::OnPaint");
  if (ShouldBrowserCustomDrawTitlebar(browser_view())) {
    PaintTitlebar(canvas);
  }
}

void BrowserFrameViewWin::Layout(PassKey) {
  TRACE_EVENT0("views.frame", "BrowserFrameViewWin::Layout");

  LayoutCaptionButtons();
  if (!browser_view()->IsWindowControlsOverlayEnabled()) {
    LayoutTitleBar();
  }
  LayoutClientView();
  LayoutSuperclass<BrowserNonClientFrameView>(this);
}

///////////////////////////////////////////////////////////////////////////////
// BrowserFrameViewWin, private:

int BrowserFrameViewWin::FrameBorderThickness() const {
  return (IsMaximized() || frame()->IsFullscreen())
             ? 0
             : display::win::GetScreenWin()->GetSystemMetricsInDIP(
                   SM_CXSIZEFRAME);
}

int BrowserFrameViewWin::FrameTopBorderThickness(bool restored) const {
  const bool is_fullscreen =
      (frame()->IsFullscreen() || IsMaximized()) && !restored;
  if (!is_fullscreen) {
    if (browser_view()->GetTabStripVisible()) {
      // Restored windows have a smaller top resize handle than the system
      // default. When maximized, the OS sizes the window such that the border
      // extends beyond the screen edges. In that case, we must return the
      // default value.
      return 0;
    }

    // There is no top border in tablet mode when the window is "restored"
    // because it is still tiled into either the left or right pane of the
    // display takes up the entire vertical extent of the screen. Note that a
    // rendering bug in Windows may still cause the very top of the window to be
    // cut off intermittently, but that's an OS issue that affects all
    // applications, not specifically Chrome.
    if (IsWebUITabStrip()) {
      return 0;
    }
  }

  // Mouse and touch locations are floored but GetSystemMetricsInDIP is rounded,
  // so we need to floor instead or else the difference will cause the hittest
  // to fail when it ought to succeed.
  return std::floor(
      FrameTopBorderThicknessPx(restored) /
      display::win::GetScreenWin()->GetScaleFactorForHWND(HWNDForView(this)));
}

int BrowserFrameViewWin::FrameTopBorderThicknessPx(bool restored) const {
  // Distinct from FrameBorderThickness() because we can't inset the top
  // border, otherwise Windows will give us a standard titlebar.
  // For maximized windows this is not true, and the top border must be
  // inset in order to avoid overlapping the monitor above.
  // See comments in BrowserDesktopWindowTreeHostWin::GetClientAreaInsets().
  const bool needs_no_border =
      (ShouldBrowserCustomDrawTitlebar(browser_view()) &&
       frame()->IsMaximized()) ||
      frame()->IsFullscreen();
  if (needs_no_border && !restored) {
    return 0;
  }

  // Note that this method assumes an equal resize handle thickness on all
  // sides of the window.
  // TODO(dfried): Consider having it return a gfx::Insets object instead.
  return ui::GetFrameThicknessFromWindow(HWNDForView(this),
                                         MONITOR_DEFAULTTONEAREST);
}

int BrowserFrameViewWin::TopAreaHeight(bool restored) const {
  if (frame()->IsFullscreen() && !restored) {
    return 0;
  }

  const bool maximized = IsMaximized() && !restored;
  int top = FrameTopBorderThickness(restored);
  if (IsWebUITabStrip()) {
    // Caption bar is default Windows size in maximized mode but full size when
    // windows are tiled in tablet mode (baesd on behavior of first-party
    // Windows applications).
    top += maximized ? TitlebarMaximizedVisualHeight()
                     : caption_button_container_->GetPreferredSize().height();
    return top;
  }

  // The tabstrip controls its own top padding.
  return top;
}

int BrowserFrameViewWin::TitlebarMaximizedVisualHeight() const {
  int maximized_height =
      display::win::GetScreenWin()->GetSystemMetricsInDIP(SM_CYCAPTION);
  // Adding 2 dip of vertical padding puts at least 1 dip of space on the top
  // and bottom of the element.
  constexpr int kVerticalPadding = 2;
  if (!browser_view()->GetWebAppFrameToolbarPreferredSize().IsEmpty()) {
    maximized_height =
        std::max(maximized_height,
                 browser_view()->GetWebAppFrameToolbarPreferredSize().height() +
                     kVerticalPadding);
  }
  return maximized_height;
}

int BrowserFrameViewWin::TitlebarHeight(bool restored) const {
  if (frame()->IsFullscreen() && !restored) {
    return 0;
  }

  // The titlebar's actual height is the same in restored and maximized, but
  // some of it is above the screen in maximized mode. See the comment in
  // FrameTopBorderThicknessPx(). For WebUI,
  return (IsWebUITabStrip()
              ? caption_button_container_->GetPreferredSize().height()
              : TitlebarMaximizedVisualHeight()) +
         FrameTopBorderThickness(false);
}

int BrowserFrameViewWin::GetFrameHeight() const {
  if (browser_view()->GetTabStripVisible()) {
    return browser_view()->tab_strip_region_view()->GetMinimumSize().height() -
           WindowTopY() - GetLayoutConstant(TABSTRIP_TOOLBAR_OVERLAP);
  }
  return IsMaximized() ? TitlebarMaximizedVisualHeight()
                       : TitlebarHeight(false);
}

int BrowserFrameViewWin::WindowTopY() const {
  // The window top is SM_CYSIZEFRAME pixels when maximized (see the comment in
  // FrameTopBorderThickness()) and floor(system dsf) pixels when restored.
  // Unfortunately we can't represent either of those at hidpi without using
  // non-integral dips, so we return the closest reasonable values instead.
  if (IsMaximized()) {
    return FrameTopBorderThickness(false);
  }
  return IsWebUITabStrip() ? FrameTopBorderThickness(true) : 1;
}

int BrowserFrameViewWin::CaptionButtonsRegionWidth() const {
  std::optional<int> system_caption_buttons_width;
  if (!base::FeatureList::IsEnabled(kAvoidUnnecessaryGetMinimizeButtonOffset)) {
    system_caption_buttons_width = width() - frame()->GetMinimizeButtonOffset();
  }

  int total_width = caption_button_container_->size().width();
  if (!ShouldBrowserCustomDrawTitlebar(browser_view())) {
    if (!system_caption_buttons_width.has_value()) {
      system_caption_buttons_width =
          width() - frame()->GetMinimizeButtonOffset();
    }
    total_width += system_caption_buttons_width.value();
  }

  return total_width;
}

bool BrowserFrameViewWin::ShouldShowWindowIcon(TitlebarType type) const {
  if (type == TitlebarType::kCustom &&
      !ShouldBrowserCustomDrawTitlebar(browser_view())) {
    return false;
  }
  if (type == TitlebarType::kSystem &&
      ShouldBrowserCustomDrawTitlebar(browser_view())) {
    return false;
  }
  if (frame()->IsFullscreen()) {
    return false;
  }
  return browser_view()->ShouldShowWindowIcon();
}

bool BrowserFrameViewWin::ShouldShowWindowTitle(TitlebarType type) const {
  if (type == TitlebarType::kCustom &&
      !ShouldBrowserCustomDrawTitlebar(browser_view())) {
    return false;
  }
  if (type == TitlebarType::kSystem &&
      ShouldBrowserCustomDrawTitlebar(browser_view())) {
    return false;
  }
  if (frame()->IsFullscreen()) {
    return false;
  }
  return browser_view()->ShouldShowWindowTitle();
}

void BrowserFrameViewWin::TabletModeChanged() {
  if (!ShouldBrowserCustomDrawTitlebar(browser_view())) {
    SetSystemMicaTitlebarAttributes();
  }
}

void BrowserFrameViewWin::SetSystemMicaTitlebarAttributes() {
  CHECK(SystemTitlebarCanUseMicaMaterial());

  const BOOL dark_titlebar_enabled =
      frame()->GetColorMode() == ui::ColorProviderKey::ColorMode::kDark;
  DwmSetWindowAttribute(views::HWNDForWidget(frame()),
                        DWMWA_USE_IMMERSIVE_DARK_MODE, &dark_titlebar_enabled,
                        sizeof(dark_titlebar_enabled));

  const DWM_SYSTEMBACKDROP_TYPE dwm_backdrop_type =
      browser_view()->GetTabStripVisible() ? DWMSBT_TABBEDWINDOW
                                           : DWMSBT_MAINWINDOW;
  DwmSetWindowAttribute(views::HWNDForWidget(frame()),
                        DWMWA_SYSTEMBACKDROP_TYPE, &dwm_backdrop_type,
                        sizeof(dwm_backdrop_type));
}

SkColor BrowserFrameViewWin::GetTitlebarColor() const {
  return GetFrameColor(BrowserFrameActiveState::kUseCurrent);
}

void BrowserFrameViewWin::PaintTitlebar(gfx::Canvas* canvas) const {
  TRACE_EVENT0("views.frame", "BrowserFrameViewWin::PaintTitlebar");

  // This is the pixel-accurate version of WindowTopY(). Scaling the DIP values
  // here compounds precision error, which exposes unpainted client area. When
  // restored it uses the system dsf instead of the per-monitor dsf to match
  // Windows' behavior.
  const int y = IsMaximized() ? FrameTopBorderThicknessPx(false)
                              : std::floor(display::win::GetDPIScale());

  // Draw the top of the accent border.
  //
  // We let the DWM do this for the other sides of the window by insetting the
  // client area to leave nonclient area available. However, along the top
  // window edge, we have to have zero nonclient area or the DWM will draw a
  // full native titlebar outside our client area. See
  // BrowserDesktopWindowTreeHostWin::GetClientAreaInsets().
  //
  // We could ask the DWM to draw the top accent border in the client area (by
  // calling DwmExtendFrameIntoClientArea() in
  // BrowserDesktopWindowTreeHostWin::UpdateDWMFrame()), but this requires
  // that we leave part of the client surface transparent. If we draw this
  // ourselves, we can make the client surface fully opaque and avoid the
  // power consumption needed for DWM to blend the window contents.
  //
  // So the accent border also has to be opaque. We can blend the titlebar
  // color with the accent border to approximate the native effect.
  const SkColor titlebar_color = GetTitlebarColor();
  gfx::ScopedCanvas scoped_canvas(canvas);
  float scale = canvas->UndoDeviceScaleFactor();
  cc::PaintFlags flags;
  flags.setColor(color_utils::GetResultingPaintColor(
      GetColorProvider()->GetColor(ShouldPaintAsActive()
                                       ? kColorAccentBorderActive
                                       : kColorAccentBorderInactive),
      titlebar_color));
  canvas->DrawRect(gfx::RectF(0, 0, width() * scale, y), flags);

  const int titlebar_height =
      browser_view()->GetTabStripVisible()
          ? GetBoundsForTabStripRegion(
                browser_view()->tab_strip_region_view()->GetMinimumSize())
                .bottom()
          : TitlebarHeight(false);
  const gfx::Rect titlebar_rect = gfx::ToEnclosingRect(
      gfx::RectF(0, y, width() * scale, titlebar_height * scale - y));
  // Paint the titlebar first so we have a background if an area isn't covered
  // by the theme image.
  flags.setColor(titlebar_color);
  canvas->DrawRect(titlebar_rect, flags);
  const gfx::ImageSkia frame_image = GetFrameImage();
  if (!frame_image.isNull()) {
    canvas->TileImageInt(frame_image, 0,
                         ThemeProperties::kFrameHeightAboveTabs -
                             GetTopInset(false) + titlebar_rect.y(),
                         titlebar_rect.x(), titlebar_rect.y(),
                         titlebar_rect.width(), titlebar_rect.height(), scale,
                         SkTileMode::kRepeat, SkTileMode::kMirror);
  }
  const gfx::ImageSkia frame_overlay_image = GetFrameOverlayImage();
  if (!frame_overlay_image.isNull()) {
    canvas->DrawImageInt(frame_overlay_image, 0, 0, frame_overlay_image.width(),
                         frame_overlay_image.height(), titlebar_rect.x(),
                         titlebar_rect.y(), frame_overlay_image.width() * scale,
                         frame_overlay_image.height() * scale, true);
  }

  if (ShouldShowWindowTitle(TitlebarType::kCustom) && window_title_) {
    window_title_->SetEnabledColor(
        GetCaptionColor(BrowserFrameActiveState::kUseCurrent));
  }
}

void BrowserFrameViewWin::LayoutTitleBar() {
  TRACE_EVENT0("views.frame", "BrowserFrameViewWin::LayoutTitleBar");
  const bool show_icon = ShouldShowWindowIcon(TitlebarType::kCustom);
  const bool show_title = ShouldShowWindowTitle(TitlebarType::kCustom);
  if (window_icon_) {
    window_icon_->SetVisible(show_icon);
  }
  if (window_title_) {
    window_title_->SetVisible(show_title);
  }
  if (!show_icon && !show_title) {
    return;
  }

  const int icon_size =
      display::win::GetScreenWin()->GetSystemMetricsInDIP(SM_CYSMICON);
  const int titlebar_visual_height =
      IsMaximized() ? TitlebarMaximizedVisualHeight() : TitlebarHeight(false);
  // Don't include the area above the screen when maximized. However it only
  // looks centered if we start from y=0 when restored.
  const int window_top = IsMaximized() ? WindowTopY() : 0;
  int next_leading_x =
      display::win::GetScreenWin()->GetSystemMetricsInDIP(SM_CXSIZEFRAME);
  if (IsMaximized()) {
    next_leading_x += kMaximizedLeftMargin;
  }
  int next_trailing_x = width() - CaptionButtonsRegionWidth();

  const int y = window_top + (titlebar_visual_height - icon_size) / 2;
  const gfx::Rect window_icon_bounds =
      gfx::Rect(next_leading_x, y, icon_size, icon_size);

  if (show_icon) {
    window_icon_->SetBoundsRect(window_icon_bounds);
    next_leading_x = window_icon_bounds.right() + kIconTitleSpacing;
  }

  if (show_title && window_title_) {
    window_title_->SetText(browser_view()->GetWindowTitle());
    const int max_text_width = std::max(0, next_trailing_x - next_leading_x);
    frame()->LayoutWebAppWindowTitle(
        gfx::Rect(next_leading_x, window_icon_bounds.y(), max_text_width,
                  window_icon_bounds.height()),
        *window_title_);
  }
}

void BrowserFrameViewWin::LayoutCaptionButtons() {
  TRACE_EVENT0("views.frame", "BrowserFrameViewWin::LayoutCaptionButtons");

  caption_button_container_->SetVisible(!frame()->IsFullscreen());

  const gfx::Size preferred_size =
      caption_button_container_->GetPreferredSize();

  const int system_caption_buttons_width =
      ShouldBrowserCustomDrawTitlebar(browser_view())
          ? 0
          : width() - frame()->GetMinimizeButtonOffset();

  const int height =
      !browser_view()->GetWebAppFrameToolbarPreferredSize().IsEmpty()
          ? (TitlebarHeight(false) - WindowTopY())
          : GetFrameHeight();

  caption_button_container_->SetBounds(
      CaptionButtonsOnLeadingEdge()
          ? system_caption_buttons_width
          : width() - system_caption_buttons_width - preferred_size.width(),
      WindowTopY(), preferred_size.width(), height);
}

void BrowserFrameViewWin::LayoutClientView() {
  client_view_bounds_ = GetLocalBounds();
  int top_inset = GetTopInset(false);
  if (browser_view()->IsWindowControlsOverlayEnabled() ||
      !browser_view()->GetWebAppFrameToolbarPreferredSize().IsEmpty()) {
    top_inset = frame()->IsFullscreen() ? 0 : WindowTopY();
  }
  client_view_bounds_.Inset(gfx::Insets::TLBR(top_inset, 0, 0, 0));
}

void BrowserFrameViewWin::StartThrobber() {
  DCHECK(ShouldShowWindowIcon(TitlebarType::kSystem));
  if (!throbber_running_) {
    throbber_running_ = true;
    throbber_frame_ = 0;
    InitThrobberIcons();
    SendMessage(views::HWNDForWidget(frame()), WM_SETICON,
                static_cast<WPARAM>(ICON_SMALL),
                reinterpret_cast<LPARAM>(throbber_icons_[throbber_frame_]));
  }
}

void BrowserFrameViewWin::StopThrobber() {
  DCHECK(ShouldShowWindowIcon(TitlebarType::kSystem));
  if (throbber_running_) {
    throbber_running_ = false;

    base::win::ScopedGDIObject<HICON> previous_small_icon;
    base::win::ScopedGDIObject<HICON> previous_big_icon;
    HICON small_icon = nullptr;
    HICON big_icon = nullptr;

    gfx::ImageSkia icon =
        browser_view()->GetWindowIcon().Rasterize(GetColorProvider());

    if (!icon.isNull()) {
      // Keep previous icons alive as long as they are referenced by the HWND.
      previous_small_icon = std::move(small_window_icon_);
      previous_big_icon = std::move(big_window_icon_);

      // Take responsibility for eventually destroying the created icons.
      small_window_icon_ = CreateHICONFromSkBitmapSizedTo(
          icon, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
      big_window_icon_ = CreateHICONFromSkBitmapSizedTo(
          icon, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON));

      small_icon = small_window_icon_.get();
      big_icon = big_window_icon_.get();
    }

    // Fallback to class icon.
    if (!small_icon) {
      small_icon = reinterpret_cast<HICON>(
          GetClassLongPtr(views::HWNDForWidget(frame()), GCLP_HICONSM));
    }
    if (!big_icon) {
      big_icon = reinterpret_cast<HICON>(
          GetClassLongPtr(views::HWNDForWidget(frame()), GCLP_HICON));
    }

    // This will reset the icon which we set in the throbber code.
    // WM_SETICON with null icon restores the icon for title bar but not
    // for taskbar. See http://crbug.com/29996
    SendMessage(views::HWNDForWidget(frame()), WM_SETICON,
                static_cast<WPARAM>(ICON_SMALL),
                reinterpret_cast<LPARAM>(small_icon));

    SendMessage(views::HWNDForWidget(frame()), WM_SETICON,
                static_cast<WPARAM>(ICON_BIG),
                reinterpret_cast<LPARAM>(big_icon));
  }
}

void BrowserFrameViewWin::DisplayNextThrobberFrame() {
  throbber_frame_ = (throbber_frame_ + 1) % kThrobberIconCount;
  SendMessage(views::HWNDForWidget(frame()), WM_SETICON,
              static_cast<WPARAM>(ICON_SMALL),
              reinterpret_cast<LPARAM>(throbber_icons_[throbber_frame_]));
}

// static
void BrowserFrameViewWin::InitThrobberIcons() {
  static bool initialized = false;
  if (!initialized) {
    for (int i = 0; i < kThrobberIconCount; ++i) {
      throbber_icons_[i] =
          ui::LoadThemeIconFromResourcesDataDLL(IDI_THROBBER_01 + i);
      DCHECK(throbber_icons_[i]);
    }
    initialized = true;
  }
}

BEGIN_METADATA(BrowserFrameViewWin)
END_METADATA