File: exclusive_access_bubble_views.cc

package info (click to toggle)
chromium 138.0.7204.183-1~deb12u1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm-proposed-updates
  • size: 6,080,960 kB
  • sloc: cpp: 34,937,079; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,954; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,811; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (366 lines) | stat: -rw-r--r-- 14,926 bytes parent folder | download | duplicates (4)
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
// 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.

#include "chrome/browser/ui/views/exclusive_access_bubble_views.h"

#include <utility>

#include "base/i18n/case_conversion.h"
#include "base/location.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
#include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/exclusive_access_bubble_views_context.h"
#include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
#include "chrome/browser/ui/views/frame/top_container_view.h"
#include "chrome/grit/generated_resources.h"
#include "components/fullscreen_control/subtle_notification_view.h"
#include "content/public/browser/web_contents.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
#include "url/origin.h"

#if BUILDFLAG(IS_WIN)
#include "ui/base/l10n/l10n_util_win.h"
#endif

namespace {

// Returns whether `type` indicates a tab-initiated fullscreen mode.
bool IsTabFullscreenType(ExclusiveAccessBubbleType type) {
  return type == EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_EXIT_INSTRUCTION ||
         type ==
             EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_POINTERLOCK_EXIT_INSTRUCTION ||
         type == EXCLUSIVE_ACCESS_BUBBLE_TYPE_KEYBOARD_LOCK_EXIT_INSTRUCTION;
}

}  // namespace

ExclusiveAccessBubbleViews::ExclusiveAccessBubbleViews(
    ExclusiveAccessBubbleViewsContext* context,
    const ExclusiveAccessBubbleParams& params,
    ExclusiveAccessBubbleHideCallback first_hide_callback)
    : ExclusiveAccessBubble(params),
      bubble_view_context_(context),
      first_hide_callback_(std::move(first_hide_callback)),
      animation_(new gfx::SlideAnimation(this)) {
  // Create the contents view.
  auto content_view = std::make_unique<SubtleNotificationView>();
  view_ = content_view.get();
  view_->SetProperty(views::kElementIdentifierKey,
                     kExclusiveAccessBubbleViewElementId);

#if BUILDFLAG(IS_CHROMEOS)
  // Technically the exit fullscreen key on ChromeOS is F11 and the
  // "Fullscreen" key on the keyboard is just translated to F11 or F4 (which
  // is also a toggle-fullscreen command on ChromeOS). However most Chromebooks
  // have media keys - including "fullscreen" - but not function keys, so
  // instructing the user to "Press [F11] to exit fullscreen" isn't useful.
  //
  // An obvious solution might be to change the primary accelerator to the
  // fullscreen key, but since translation to a function key is done at system
  // level we can't actually do that. Instead we provide specific messaging for
  // the platform here. (See crbug.com/1110468 for details.)
  browser_fullscreen_exit_accelerator_ =
      l10n_util::GetStringUTF16(IDS_APP_FULLSCREEN_KEY);
#else
  ui::Accelerator accelerator(ui::VKEY_UNKNOWN, ui::EF_NONE);
  bool got_accelerator =
      bubble_view_context_->GetAcceleratorProvider()
          ->GetAcceleratorForCommandId(IDC_FULLSCREEN, &accelerator);
  DCHECK(got_accelerator);
  browser_fullscreen_exit_accelerator_ = accelerator.GetShortcutText();
#endif

  UpdateViewContent(params_.type);

  // Initialize the popup.
  popup_ = SubtleNotificationView::CreatePopupWidget(
      bubble_view_context_->GetBubbleParentView(), std::move(content_view));

  gfx::Rect popup_rect = GetPopupRect();
  gfx::Size size = popup_rect.size();
  // Bounds are in screen coordinates.
  popup_->SetBounds(popup_rect);
  // Why is this special enough to require the "security surface" level? A
  // decision was made a long time ago to not require confirmation when a site
  // asks to go fullscreen, and that's not changing. However, a site going
  // fullscreen is a big security risk, allowing phishing and other UI fakery.
  // This bubble is the only defense that Chromium can provide against this
  // attack, so it's important to order it above everything.
  //
  // On some platforms, pages can put themselves into fullscreen and then
  // trigger other elements to cover up this bubble, elements that aren't fully
  // under Chromium's control. See https://crbug.com/927150 for an example.
  popup_->SetZOrderLevel(ui::ZOrderLevel::kSecuritySurface);
  view_->SetBounds(0, 0, size.width(), size.height());
  popup_->AddObserver(this);

  ShowAndStartTimers();

  const bool entering_tab_fullscreen = IsTabFullscreenType(params.type);
  // If the tab enters fullscreen without any recent user interaction, re-show
  // the bubble on the first user input event, by clearing the snooze time.
  content::WebContents* tab = bubble_view_context_->GetExclusiveAccessManager()
                                  ->fullscreen_controller()
                                  ->exclusive_access_tab();
  if (entering_tab_fullscreen && tab && !tab->HasRecentInteraction()) {
    snooze_until_ = base::TimeTicks::Min();
  }
}

ExclusiveAccessBubbleViews::~ExclusiveAccessBubbleViews() {
  RunHideCallbackIfNeeded(ExclusiveAccessBubbleHideReason::kInterrupted);

  popup_->RemoveObserver(this);

  // This is tricky.  We may be in an ATL message handler stack, in which case
  // the popup cannot be deleted yet.  We also can't set the popup's ownership
  // model to NATIVE_WIDGET_OWNS_WIDGET because if the user closed the last tab
  // while in fullscreen mode, Windows has already destroyed the popup HWND by
  // the time we get here, and thus either the popup will already have been
  // deleted (if we set this in our constructor) or the popup will never get
  // another OnFinalMessage() call (if not, as currently).  So instead, we tell
  // the popup to synchronously hide, and then asynchronously close and delete
  // itself.
  popup_->Close();
  base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(FROM_HERE,
                                                                popup_.get());
  CHECK(!views::WidgetObserver::IsInObserverList());
}

void ExclusiveAccessBubbleViews::Update(
    const ExclusiveAccessBubbleParams& params,
    ExclusiveAccessBubbleHideCallback first_hide_callback) {
  DCHECK(EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE != params.type ||
         params.has_download);
  bool already_shown = IsShowing() || IsVisible();
  if (params_.type == params.type && params_.origin == params.origin &&
      !params.force_update && already_shown) {
    return;
  }

  // Show the notification about overriding only if:
  // 1. There was a notification visible earlier, and
  // 2. Exactly one of the previous and current notifications has a download,
  //    or the previous notification was about an override itself.
  // If both the previous and current notifications have a download, but
  // neither is an override, then we don't need to show an override.
  notify_overridden_ =
      already_shown &&
      (notify_overridden_ || (params.has_download ^ params_.has_download));
  params_.has_download = params.has_download || notify_overridden_;

  // Bubble maybe be re-used after timeout.
  RunHideCallbackIfNeeded(ExclusiveAccessBubbleHideReason::kInterrupted);

  first_hide_callback_ = std::move(first_hide_callback);

  const bool entering_tab_fullscreen =
      !IsTabFullscreenType(params_.type) && IsTabFullscreenType(params.type);

  params_.origin = params.origin;
  // When a request to notify about a download is made, the bubble type
  // should be preserved from the old value, and not be updated.
  if (!params.has_download) {
    params_.type = params.type;
  }
  UpdateViewContent(params_.type);
  view_->SizeToPreferredSize();
  popup_->SetBounds(GetPopupRect());
  ShowAndStartTimers();

  // If the tab enters fullscreen without any recent user interaction, re-show
  // the bubble on the first user input event, by clearing the snooze time.
  content::WebContents* tab = bubble_view_context_->GetExclusiveAccessManager()
                                  ->fullscreen_controller()
                                  ->exclusive_access_tab();
  if (entering_tab_fullscreen && tab && !tab->HasRecentInteraction()) {
    snooze_until_ = base::TimeTicks::Min();
  }
}

void ExclusiveAccessBubbleViews::RepositionIfVisible() {
  if (IsVisible()) {
    UpdateBounds();
  }
}

void ExclusiveAccessBubbleViews::HideImmediately() {
  if (!IsShowing() && !popup_->IsVisible()) {
    return;
  }

  RunHideCallbackIfNeeded(ExclusiveAccessBubbleHideReason::kInterrupted);

  animation_->SetSlideDuration(base::Milliseconds(150));
  animation_->Hide();
}

bool ExclusiveAccessBubbleViews::IsShowing() const {
  return animation_->is_animating() && animation_->IsShowing();
}

views::View* ExclusiveAccessBubbleViews::GetView() {
  return view_;
}

void ExclusiveAccessBubbleViews::UpdateBounds() {
  gfx::Rect popup_rect(GetPopupRect());
  if (!popup_rect.IsEmpty()) {
    popup_->SetBounds(popup_rect);
    view_->SetY(popup_rect.height() - view_->height());
  }
}

void ExclusiveAccessBubbleViews::UpdateViewContent(
    ExclusiveAccessBubbleType bubble_type) {
  DCHECK(params_.has_download ||
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE != bubble_type);

  std::u16string accelerator;
  bool should_show_browser_acc =
      (params_.has_download &&
       bubble_type == EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE) ||
      exclusive_access_bubble::IsExclusiveAccessModeBrowserFullscreen(
          bubble_type);
  if (should_show_browser_acc &&
      !base::FeatureList::IsEnabled(
          features::kPressAndHoldEscToExitBrowserFullscreen)) {
    accelerator = browser_fullscreen_exit_accelerator_;
  } else {
    accelerator = l10n_util::GetStringUTF16(IDS_APP_ESC_KEY);

#if BUILDFLAG(IS_MAC)
    // Mac keyboards use lowercase for the non-letter keys, and since the key is
    // placed in a box to make it look like a keyboard key it looks weird to not
    // follow suit.
    accelerator = base::i18n::ToLower(accelerator);
#endif
  }
  // This string *may* contain the name of the key surrounded in pipe characters
  // ('|'), which should be drawn graphically as a key, not displayed literally.
  // `accelerator` is the name of the key to exit fullscreen mode.
  view_->UpdateContent(exclusive_access_bubble::GetInstructionTextForType(
      params_.type, accelerator, params_.origin, params_.has_download,
      notify_overridden_));
}

bool ExclusiveAccessBubbleViews::IsVisible() const {
#if BUILDFLAG(IS_MAC)
  // Due to a quirk on the Mac, the popup will not be visible for a short period
  // of time after it is shown (it's asynchronous) so if we don't check the
  // value of the animation we'll have a stale version of the bounds when we
  // show it and it will appear in the wrong place - typically where the window
  // was located before going to fullscreen.
  return (popup_->IsVisible() || animation_->GetCurrentValue() > 0.0);
#else
  return (popup_->IsVisible());
#endif
}

void ExclusiveAccessBubbleViews::AnimationProgressed(
    const gfx::Animation* animation) {
  float opacity = static_cast<float>(animation_->CurrentValueBetween(0.0, 1.0));
  if (opacity == 0) {
    popup_->Hide();
  } else {
    popup_->Show();
    popup_->SetOpacity(opacity);
  }
}

void ExclusiveAccessBubbleViews::AnimationEnded(
    const gfx::Animation* animation) {
  if (animation_->IsShowing()) {
    GetView()->NotifyAccessibilityEventDeprecated(ax::mojom::Event::kAlert,
                                                  true);
  }
  AnimationProgressed(animation);
}

gfx::Rect ExclusiveAccessBubbleViews::GetPopupRect() const {
  gfx::Size size(view_->GetPreferredSize());
  gfx::Rect widget_bounds = bubble_view_context_->GetClientAreaBoundsInScreen();
  int x = widget_bounds.x() + (widget_bounds.width() - size.width()) / 2;

  int top_container_bottom = widget_bounds.y();
#if !BUILDFLAG(IS_MAC)
  if (bubble_view_context_->IsImmersiveModeEnabled()) {
    // Skip querying the top container height in CrOS non-immersive fullscreen
    // because:
    // - The top container height is always zero in non-immersive fullscreen.
    // - Querying the top container height may return the height before entering
    //   fullscreen because layout is disabled while entering fullscreen.
    // A visual glitch due to the delayed layout is avoided in immersive
    // fullscreen because entering fullscreen starts with the top container
    // revealed. When revealed, the top container has the same height as before
    // entering fullscreen.
    top_container_bottom =
        bubble_view_context_->GetTopContainerBoundsInScreen().bottom();
  }
#endif
  // Space between top of screen and popup.
  static constexpr int kPopupTopPx = 45;
  // |desired_top| is the top of the bubble area including the shadow.
  const int desired_top = kPopupTopPx - view_->GetInsets().top();
  const int y = top_container_bottom + desired_top;

  return gfx::Rect(gfx::Point(x, y), size);
}

void ExclusiveAccessBubbleViews::Hide() {
  // This function is guarded by the `ExclusiveAccessBubble::hide_timeout_`
  // timer, so the bubble has been displayed for at least
  // `ExclusiveAccessBubble::kShowTime`.
  DCHECK(!hide_timeout_.IsRunning());
  RunHideCallbackIfNeeded(ExclusiveAccessBubbleHideReason::kTimeout);

  animation_->SetSlideDuration(base::Milliseconds(700));
  animation_->Hide();
}

void ExclusiveAccessBubbleViews::Show() {
  if (animation_->IsShowing()) {
    return;
  }
  animation_->SetSlideDuration(base::Milliseconds(350));
  animation_->Show();
}

void ExclusiveAccessBubbleViews::OnWidgetDestroyed(views::Widget* widget) {
  // Although SubtleNotificationView uses WIDGET_OWNS_NATIVE_WIDGET, a close can
  // originate from the OS or some Chrome shutdown codepaths that bypass the
  // destructor.
  views::Widget* popup_on_stack = popup_;
  DCHECK(popup_on_stack->HasObserver(this));

  // Get ourselves destroyed. Calling ExitExclusiveAccess() won't work because
  // the parent window might be destroyed as well, so asking it to exit
  // fullscreen would be a bad idea.
  bubble_view_context_->DestroyAnyExclusiveAccessBubble();

  // Note: |this| is destroyed on the line above. Check that the destructor was
  // invoked. This is safe to do since |popup_| is deleted via a posted task.
  DCHECK(!popup_on_stack->HasObserver(this));
}

void ExclusiveAccessBubbleViews::RunHideCallbackIfNeeded(
    ExclusiveAccessBubbleHideReason reason) {
  if (first_hide_callback_) {
    std::move(first_hide_callback_).Run(reason);
  }
}