File: compose_dialog_view.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 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 (233 lines) | stat: -rw-r--r-- 8,933 bytes parent folder | download | duplicates (6)
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
// Copyright 2023 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/compose/compose_dialog_view.h"

#include <vector>

#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/renderer_context_menu/render_view_context_menu.h"
#include "components/compose/core/browser/config.h"
#include "components/renderer_context_menu/context_menu_delegate.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/menus/simple_menu_model.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/view_class_properties.h"

namespace {

gfx::Rect FallbackAtBottomShiftUpToPermanentPosition(
    gfx::Rect best_location,
    const gfx::Rect& screen_work_area,
    const gfx::Size& widget_size) {
  // Make sure all sizes of the view will fit in the work area
  best_location.AdjustToFit(screen_work_area);

  // Now make best_location reflect the actual size of the widget.
  best_location.set_size(widget_size);

  return best_location;
}

gfx::Rect FallbackAtBottomShiftMinimumForVisibility(gfx::Rect best_location) {
  // Just use the initial location anchored at bottom left directly.
  return best_location;
}

gfx::Rect FallbackCenterOnFormFieldStrategy(const gfx::Rect& screen_work_are,
                                            const gfx::Size& widget_size,
                                            const gfx::Rect& anchor_rect) {
  gfx::Rect widget_rect;
  widget_rect.set_size(widget_size);
  gfx::Vector2d translation = anchor_rect.CenterPoint().OffsetFromOrigin() -
                              widget_rect.CenterPoint().OffsetFromOrigin();

  widget_rect.Offset(translation);
  return widget_rect;
}

}  // namespace

DEFINE_ELEMENT_IDENTIFIER_VALUE(kComposeWebviewElementId);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(ComposeDialogView, kComposeDialogId);

// static
gfx::Rect ComposeDialogView::CalculateBubbleBounds(
    gfx::Rect screen_work_area,
    gfx::Size widget_size,
    gfx::Rect anchor_bounds,
    std::optional<gfx::Rect> parent_bounds) {
  // Widget can't be smaller than a minimum size.
  widget_size.SetToMax({kComposeMaxDialogWidthPx, kComposeMinDialogHeightPx});

  // We don't want to render anything within `padding` pixels of the edge of the
  // screen work area.
  screen_work_area.Inset(kComposeDialogWorkAreaPadding);

  // If the param to stay in the window bounds is true, and the window is large
  // enough, use that as the work area instead.
  if (compose::GetComposeConfig().stay_in_window_bounds &&
      parent_bounds.has_value()) {
    if ((widget_size.width() <= parent_bounds->width() &&
         widget_size.height() <= parent_bounds->height())) {
      screen_work_area = parent_bounds.value();
    }
  }

  // We don't want to render anything within `padding` pixels of the edge of the
  // anchor rect.  But we will if we have to (due to AdjustToFit below).
  anchor_bounds.Outset(kComposeDialogAnchorPadding);

  // Available space measures the distance from each side of the padded work
  // area to the edge of the padded anchor (plus padding).
  gfx::Insets available_space = screen_work_area.InsetsFrom(anchor_bounds);

  // Ideally we render at the bottom left of the anchor. If the dialog would be
  // offscreen, we reposition it.
  gfx::Rect best_location(
      anchor_bounds.bottom_left(),
      gfx::Size(kComposeMaxDialogWidthPx, kComposeMaxDialogHeightPx));

  bool space_below = available_space.bottom() >= kComposeMaxDialogHeightPx;
  bool space_above = available_space.top() >= kComposeMaxDialogHeightPx;

  // the position of the dialog is determined by finding a suitable location for
  // a maximum-sized ComposeDialogView, so that we know that the dialog doesn't
  // have to switch sides as it resizes. If the dialog is smaller than max, we
  // need to apply an alignment within that larger rectangle.

  if (!space_below) {
    // Not enough room in the preferred location. Try above the anchor.
    if (space_above) {
      // If it is laid out above the anchor rect then it
      // will be bottom-aligned.
      best_location.set_y(anchor_bounds.y() - widget_size.height());
    } else {
      // If not enough space above or below, try one of the following backup
      // strategies:

      switch (compose::GetComposeConfig().positioning_strategy) {
        case compose::DialogFallbackPositioningStrategy::kCenterOnAnchorRect:
          best_location = FallbackCenterOnFormFieldStrategy(
              screen_work_area, widget_size, anchor_bounds);
          break;
        case compose::DialogFallbackPositioningStrategy::kShiftUpUntilOnscreen:
          best_location =
              FallbackAtBottomShiftMinimumForVisibility(best_location);
          break;
        case compose::DialogFallbackPositioningStrategy::
            kShiftUpUntilMaxSizeIsOnscreen:
        default:
          best_location = FallbackAtBottomShiftUpToPermanentPosition(
              best_location, screen_work_area, widget_size);
          break;
      }
    }
  }

  // Always use the size that the WebUI wants to be in the end.
  best_location.set_size(widget_size);

  // Always remain completely on screen within the provided insets.
  best_location.AdjustToFit(screen_work_area);

  return best_location;
}

ComposeDialogView::~ComposeDialogView() = default;

ComposeDialogView::ComposeDialogView(
    View* anchor_view,
    std::unique_ptr<WebUIContentsWrapperT<ComposeUntrustedUI>> bubble_wrapper,
    const gfx::Rect& anchor_bounds,
    views::BubbleBorder::Arrow anchor_position)
    : WebUIBubbleDialogView(anchor_view,
                            bubble_wrapper->GetWeakPtr(),
                            anchor_bounds,
                            anchor_position),
      anchor_bounds_(anchor_bounds),
      bubble_wrapper_(std::move(bubble_wrapper)) {
  SetProperty(views::kElementIdentifierKey, kComposeDialogId);
  web_view()->SetProperty(views::kElementIdentifierKey,
                          kComposeWebviewElementId);
}

void ComposeDialogView::OnBeforeBubbleWidgetInit(
    views::Widget::InitParams* params,
    views::Widget* widget) const {
  WebUIBubbleDialogView::OnBeforeBubbleWidgetInit(params, widget);
#if BUILDFLAG(IS_LINUX)
  // In linux, windows may be clipped to their anchors' bounds,
  // resulting in visual errors, unless they use accelerated rendering. See
  // crbug.com/1445770 for details.
  params->use_accelerated_widget_override = true;
#endif
}

gfx::Rect ComposeDialogView::GetBubbleBounds() {
  gfx::Size widget_size = BubbleDialogDelegateView::GetBubbleBounds().size();

  std::optional<gfx::Rect> parent_bounds;
  if (GetWidget()->parent()) {
    parent_bounds = GetWidget()->parent()->GetWindowBoundsInScreen();
  }

  display::Display display =
      display::Screen::GetScreen()->GetDisplayNearestView(
          GetAnchorView()->GetWidget()->GetNativeView());
  gfx::Rect screen_work_area = display.work_area();

  return CalculateBubbleBounds(screen_work_area, widget_size, anchor_bounds_,
                               parent_bounds);
}

bool ComposeDialogView::HandleContextMenu(
    content::RenderFrameHost& render_frame_host,
    const content::ContextMenuParams& params) {
  ContextMenuDelegate* menu_delegate = ContextMenuDelegate::FromWebContents(
      content::WebContents::FromRenderFrameHost(&render_frame_host));
  DCHECK(menu_delegate);

  std::unique_ptr<RenderViewContextMenuBase> menu =
      menu_delegate->BuildMenu(render_frame_host, params);
  // Remove everything that is not copy, paste, or cut or spellcheck
  // suggestions.
  std::vector<int> command_ids;
  for (size_t index = 0; index < menu->menu_model().GetItemCount(); index++) {
    int command_id = menu->menu_model().GetCommandIdAt(index);
    if ((command_id < IDC_CONTENT_CONTEXT_COPY ||
         command_id > IDC_CONTENT_CONTEXT_PASTE_AND_MATCH_STYLE) &&
        (command_id < IDC_SPELLCHECK_SUGGESTION_0 ||
         command_id > IDC_SPELLCHECK_SUGGESTION_LAST) &&
        command_id != IDC_CONTENT_CONTEXT_INSPECTELEMENT && command_id > 0) {
      command_ids.push_back(command_id);
    }
  }

  for (int command_id : command_ids) {
    menu->RemoveMenuItem(command_id);
  }
  menu->RemoveAdjacentSeparators();

  // There's no method to remove the final separator if there is one, so we have
  // to hack around it.
  menu->RemoveSeparatorBeforeMenuItem(IDC_CONTENT_CONTEXT_INSPECTELEMENT);
  menu->RemoveMenuItem(IDC_CONTENT_CONTEXT_INSPECTELEMENT);

  // Only show the menu if there are items in it.
  if (menu->menu_model().GetItemCount() > 0) {
    menu_delegate->ShowMenu(std::move(menu));
  }
  return true;
}

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

BEGIN_METADATA(ComposeDialogView)
END_METADATA