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);
}
}
|