Replace UILockBubble with SubtleNotificationView used by chrome
Reusing code to guarantee that we get the same visual look.
One particular difference is to have the box around the accelerator
name in the string "Press and hold [Esc] to exit full screen".
Bug: 1198400
Change-Id: I532e49d6c3d0a2b3c4dbce82c6ccd6125eeb90fe
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2823430
Reviewed-by: Ted Choc <[email protected]>
Reviewed-by: Yuwei Huang <[email protected]>
Reviewed-by: Peter Boström <[email protected]>
Reviewed-by: Mitsuru Oshima (Slow - Gardening) <[email protected]>
Commit-Queue: Joel Hockey <[email protected]>
Cr-Commit-Position: refs/heads/master@{#872297}
diff --git a/components/exo/BUILD.gn b/components/exo/BUILD.gn
index d8307dc..7d2ecde 100644
--- a/components/exo/BUILD.gn
+++ b/components/exo/BUILD.gn
@@ -133,6 +133,8 @@
"//chromeos/crosapi/cpp",
"//chromeos/ui/base",
"//chromeos/ui/frame",
+ "//components/fullscreen_control",
+ "//components/strings",
"//ui/base",
"//ui/events/ozone/layout",
]
@@ -170,8 +172,6 @@
"toast_surface.cc",
"toast_surface.h",
"toast_surface_manager.h",
- "ui_lock_bubble.cc",
- "ui_lock_bubble.h",
"ui_lock_controller.cc",
"ui_lock_controller.h",
"wm_helper_chromeos.cc",
diff --git a/components/exo/DEPS b/components/exo/DEPS
index b46bea3..279923c 100644
--- a/components/exo/DEPS
+++ b/components/exo/DEPS
@@ -4,6 +4,8 @@
"+chromeos/crosapi/cpp/crosapi_constants.h",
"+chromeos/ui/base",
"+chromeos/ui/frame",
+ "+components/fullscreen_control",
+ "+components/strings",
"+components/viz/common",
"+components/viz/host",
"+device/gamepad",
diff --git a/components/exo/ui_lock_bubble.cc b/components/exo/ui_lock_bubble.cc
deleted file mode 100644
index e34da64d..0000000
--- a/components/exo/ui_lock_bubble.cc
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/exo/ui_lock_bubble.h"
-
-#include <memory>
-
-#include "ash/strings/grit/ash_strings.h"
-#include "ash/wm/desks/desks_util.h"
-#include "components/exo/surface.h"
-#include "components/exo/wm_helper.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/gfx/canvas.h"
-#include "ui/views/border.h"
-#include "ui/views/bubble/bubble_dialog_delegate_view.h"
-#include "ui/views/controls/label.h"
-#include "ui/views/layout/fill_layout.h"
-#include "ui/views/layout/layout_provider.h"
-#include "ui/views/metadata/metadata_impl_macros.h"
-#include "ui/views/style/typography.h"
-#include "ui/views/view.h"
-#include "ui/views/widget/widget.h"
-
-namespace exo {
-namespace {
-
-constexpr int kPaddingTop = 100;
-
-} // namespace
-
-UILockBubbleView::~UILockBubbleView() = default;
-
-UILockBubbleView::UILockBubbleView(views::View* anchor_view)
- : BubbleDialogDelegateView(anchor_view, views::BubbleBorder::TOP_CENTER) {
- set_margins(views::LayoutProvider::Get()->GetInsetsMetric(
- views::InsetsMetric::INSETS_DIALOG));
- SetCanActivate(false);
- SetButtons(ui::DIALOG_BUTTON_NONE);
- set_color(SK_ColorBLACK);
-
- SetLayoutManager(std::make_unique<views::FillLayout>());
-
- views::Label* text_label = AddChildView(
- std::make_unique<views::Label>(std::u16string(l10n_util::GetStringUTF16(
- IDS_EXO_UI_LOCK_NOTIFICATION_BUBBLE_MESSAGE))));
- text_label->SetEnabledColor(SK_ColorWHITE);
- text_label->SetBackgroundColor(SK_ColorBLACK);
- text_label->SetAutoColorReadabilityEnabled(true);
- text_label->SetHorizontalAlignment(gfx::ALIGN_CENTER);
- set_anchor_view_insets(gfx::Insets(kPaddingTop, 0, 0, 0));
-}
-
-// TODO(crbug.com/1178861): Refactor this functionality to be provided by Ash
-// directly
-views::Widget* UILockBubbleView::DisplayBubble(views::View* bubble_anchor) {
- views::Widget* bubble_view_widget =
- views::BubbleDialogDelegateView::CreateBubble(
- new UILockBubbleView(bubble_anchor));
- bubble_view_widget->SetOpacity(0.5);
- bubble_view_widget->SetZOrderLevel(ui::ZOrderLevel::kSecuritySurface);
- bubble_view_widget->Show();
- return bubble_view_widget;
-}
-
-BEGIN_METADATA(UILockBubbleView, views::BubbleDialogDelegateView)
-END_METADATA
-
-} // namespace exo
diff --git a/components/exo/ui_lock_bubble.h b/components/exo/ui_lock_bubble.h
deleted file mode 100644
index cf59389..0000000
--- a/components/exo/ui_lock_bubble.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_EXO_UI_LOCK_BUBBLE_H_
-#define COMPONENTS_EXO_UI_LOCK_BUBBLE_H_
-
-#include "base/macros.h"
-#include "ui/views/bubble/bubble_dialog_delegate_view.h"
-#include "ui/views/view.h"
-#include "ui/views/widget/widget.h"
-
-namespace exo {
-
-// This view displays a Bubble within the always on top container instructing a
-// user on how to exit non-immersive fullscreen or 'gaming mode'.
-// TODO(b/161952658): Use ShellSurfaceBase::AddOverlay() rather than
-// BubbleDialogDelegateView.
-class UILockBubbleView : public views::BubbleDialogDelegateView {
- public:
- METADATA_HEADER(UILockBubbleView);
- ~UILockBubbleView() override;
- UILockBubbleView(const UILockBubbleView&) = delete;
- UILockBubbleView& operator=(const UILockBubbleView&) = delete;
-
- static views::Widget* DisplayBubble(views::View* anchor_view);
-
- private:
- UILockBubbleView(views::View* anchor_view);
-};
-
-} // namespace exo
-
-#endif // COMPONENTS_EXO_UI_LOCK_BUBBLE_H_
diff --git a/components/exo/ui_lock_controller.cc b/components/exo/ui_lock_controller.cc
index 5643675a..8d4e993 100644
--- a/components/exo/ui_lock_controller.cc
+++ b/components/exo/ui_lock_controller.cc
@@ -17,17 +17,42 @@
#include "components/exo/seat.h"
#include "components/exo/shell_surface_util.h"
#include "components/exo/surface.h"
-#include "components/exo/ui_lock_bubble.h"
#include "components/exo/wm_helper.h"
+#include "components/fullscreen_control/subtle_notification_view.h"
+#include "components/strings/grit/components_strings.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
+#include "ui/base/l10n/l10n_util.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/dom/dom_code.h"
+#include "ui/strings/grit/ui_strings.h"
#include "ui/views/widget/widget.h"
namespace {
-constexpr auto kEscHoldMessageDuration = base::TimeDelta::FromSeconds(4);
+// The Esc hold bubble shows a message to press and hold Esc to exit fullscreen.
+// The bubble will hide after a 4s timeout and will not display again for that
+// window even if it toggles fullscreen.
+
+// Duration to show the 'Press and hold Esc' bubble.
+constexpr auto kEscNotifyBubbleDuration = base::TimeDelta::FromSeconds(4);
+// Position of Esc notify bubble from top of screen.
+const int kEscNotifyBubbleTopPx = 45;
+
+// Create and position Esc notify bubble.
+views::Widget* CreateEscNotifyBubble(aura::Window* parent) {
+ auto content_view = std::make_unique<SubtleNotificationView>();
+ std::u16string accelerator = l10n_util::GetStringUTF16(IDS_APP_ESC_KEY);
+ content_view->UpdateContent(l10n_util::GetStringFUTF16(
+ IDS_FULLSCREEN_HOLD_ESC_TO_EXIT_FULLSCREEN, accelerator));
+ gfx::Size size = content_view->GetPreferredSize();
+ views::Widget* popup = SubtleNotificationView::CreatePopupWidget(
+ parent, std::move(content_view));
+ popup->SetZOrderLevel(ui::ZOrderLevel::kSecuritySurface);
+ int x = (parent->bounds().width() - size.width()) / 2;
+ popup->SetBounds(gfx::Rect(gfx::Point(x, kEscNotifyBubbleTopPx), size));
+ return popup;
+}
// Shows 'Press and hold ESC to exit fullscreen' message.
class EscHoldNotifier : public ash::WindowStateObserver {
@@ -42,58 +67,55 @@
EscHoldNotifier(const EscHoldNotifier&) = delete;
EscHoldNotifier& operator=(const EscHoldNotifier&) = delete;
- ~EscHoldNotifier() override { CloseBubble(); }
+ ~EscHoldNotifier() override { CloseEscNotifyBubble(); }
- views::Widget* bubble() { return bubble_; }
+ views::Widget* esc_notify_bubble() { return esc_notify_bubble_; }
private:
// Overridden from ash::WindowStateObserver:
- void OnPreWindowStateTypeChange(ash::WindowState* window_state,
- chromeos::WindowStateType old_type) override {
- }
void OnPostWindowStateTypeChange(
ash::WindowState* window_state,
chromeos::WindowStateType old_type) override {
if (window_state->IsFullscreen()) {
ShowBubble(window_state->window());
} else {
- CloseBubble();
+ CloseEscNotifyBubble();
}
}
void ShowBubble(aura::Window* window) {
- // Only show message once per window.
- if (has_been_shown_)
+ // Only show Esc notify bubble once per window.
+ if (esc_notify_bubble_shown_)
return;
- views::Widget* widget =
- views::Widget::GetTopLevelWidgetForNativeView(window);
- if (!widget)
- return;
- bubble_ = exo::UILockBubbleView::DisplayBubble(widget->GetContentsView());
+ if (!esc_notify_bubble_)
+ esc_notify_bubble_ = CreateEscNotifyBubble(window);
+ esc_notify_bubble_->Show();
- // Close bubble after 4s.
- close_timer_.Start(
- FROM_HERE, kEscHoldMessageDuration,
- base::BindOnce(&EscHoldNotifier::CloseBubble, base::Unretained(this),
+ // Close Esc notify bubble after 4s.
+ esc_notify_bubble_timer_.Start(
+ FROM_HERE, kEscNotifyBubbleDuration,
+ base::BindOnce(&EscHoldNotifier::CloseEscNotifyBubble,
+ base::Unretained(this),
/*closed_by_timer=*/true));
}
- void CloseBubble(bool closed_by_timer = false) {
- if (bubble_) {
- bubble_->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
- bubble_ = nullptr;
- // Message is only shown once as long as it shows for the full 4s.
+ void CloseEscNotifyBubble(bool closed_by_timer = false) {
+ if (esc_notify_bubble_) {
+ esc_notify_bubble_->CloseWithReason(
+ views::Widget::ClosedReason::kUnspecified);
+ esc_notify_bubble_ = nullptr;
+ // Esc notify bubble is not reshown after it is closed by the timer.
if (closed_by_timer) {
- has_been_shown_ = true;
+ esc_notify_bubble_shown_ = true;
window_state_observation_.Reset();
}
}
}
- views::Widget* bubble_ = nullptr;
- bool has_been_shown_ = false;
- base::OneShotTimer close_timer_;
+ views::Widget* esc_notify_bubble_ = nullptr;
+ bool esc_notify_bubble_shown_ = false;
+ base::OneShotTimer esc_notify_bubble_timer_;
base::ScopedObservation<ash::WindowState, ash::WindowStateObserver>
window_state_observation_{this};
};
@@ -165,7 +187,7 @@
}
bool UILockController::IsBubbleVisibleForTesting(aura::Window* window) {
- return window->GetProperty(kEscHoldNotifierKey)->bubble();
+ return window->GetProperty(kEscHoldNotifierKey)->esc_notify_bubble();
}
namespace {
diff --git a/components/exo/ui_lock_controller_unittest.cc b/components/exo/ui_lock_controller_unittest.cc
index 2ff813f..5dd5994 100644
--- a/components/exo/ui_lock_controller_unittest.cc
+++ b/components/exo/ui_lock_controller_unittest.cc
@@ -16,7 +16,6 @@
#include "components/exo/surface.h"
#include "components/exo/test/exo_test_base.h"
#include "components/exo/test/exo_test_helper.h"
-#include "components/exo/ui_lock_bubble.h"
#include "components/exo/wm_helper.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/class_property.h"
diff --git a/components/fullscreen_control/BUILD.gn b/components/fullscreen_control/BUILD.gn
index f3c4d2c..b3cd5bd 100644
--- a/components/fullscreen_control/BUILD.gn
+++ b/components/fullscreen_control/BUILD.gn
@@ -8,6 +8,8 @@
"fullscreen_control_popup.h",
"fullscreen_control_view.cc",
"fullscreen_control_view.h",
+ "subtle_notification_view.cc",
+ "subtle_notification_view.h",
]
deps = [
"//base",
@@ -15,6 +17,7 @@
"//components/strings",
"//components/vector_icons",
"//skia",
+ "//ui/accessibility:ax_base",
"//ui/base",
"//ui/compositor",
"//ui/gfx:native_widget_types",
diff --git a/components/fullscreen_control/subtle_notification_view.cc b/components/fullscreen_control/subtle_notification_view.cc
new file mode 100644
index 0000000..0d87d8e
--- /dev/null
+++ b/components/fullscreen_control/subtle_notification_view.cc
@@ -0,0 +1,213 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/fullscreen_control/subtle_notification_view.h"
+
+#include <memory>
+
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/accessibility/ax_node_data.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/font_list.h"
+#include "ui/gfx/geometry/insets.h"
+#include "ui/views/bubble/bubble_border.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/metadata/metadata_header_macros.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
+#include "ui/views/style/typography.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+
+// Space between the site info label.
+const int kMiddlePaddingPx = 30;
+
+const int kOuterPaddingHorizPx = 40;
+const int kOuterPaddingVertPx = 8;
+
+// Partially-transparent background color.
+const SkColor kSubtleNotificationBackgroundColor =
+ SkColorSetARGB(0xcc, 0x28, 0x2c, 0x32);
+
+// Spacing around the key name.
+const int kKeyNameMarginHorizPx = 7;
+const int kKeyNameBorderPx = 1;
+const int kKeyNameCornerRadius = 2;
+const int kKeyNamePaddingPx = 5;
+
+// The context used to obtain typography for the instruction text. It's not
+// really a dialog, but a dialog title is a good fit.
+constexpr int kInstructionTextContext = views::style::CONTEXT_DIALOG_TITLE;
+
+// Delimiter indicating there should be a segment displayed as a keyboard key.
+const char kKeyNameDelimiter[] = "|";
+
+} // namespace
+
+// Class containing the instruction text. Contains fancy styling on the keyboard
+// key (not just a simple label).
+class SubtleNotificationView::InstructionView : public views::View {
+ public:
+ METADATA_HEADER(InstructionView);
+ // Creates an InstructionView with specific text. |text| may contain one or
+ // more segments delimited by a pair of pipes ('|'); each of these segments
+ // will be displayed as a keyboard key. e.g., "Press |Alt|+|Q| to exit" will
+ // have "Alt" and "Q" rendered as keys.
+ explicit InstructionView(const std::u16string& text);
+ InstructionView(const InstructionView&) = delete;
+ InstructionView& operator=(const InstructionView&) = delete;
+
+ std::u16string GetText() const;
+ void SetText(const std::u16string& text);
+
+ private:
+ // Adds a label to the end of the notification text. If |format_as_key|,
+ // surrounds the label in a rounded-rect border to indicate that it is a
+ // keyboard key.
+ void AddTextSegment(const std::u16string& text, bool format_as_key);
+
+ std::u16string text_;
+};
+
+SubtleNotificationView::InstructionView::InstructionView(
+ const std::u16string& text) {
+ // The |between_child_spacing| is the horizontal margin of the key name.
+ SetLayoutManager(std::make_unique<views::BoxLayout>(
+ views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
+ kKeyNameMarginHorizPx));
+
+ SetText(text);
+}
+
+std::u16string SubtleNotificationView::InstructionView::GetText() const {
+ return text_;
+}
+
+void SubtleNotificationView::InstructionView::SetText(
+ const std::u16string& text) {
+ // Avoid replacing the contents with the same text.
+ if (text == text_)
+ return;
+
+ RemoveAllChildViews(true);
+
+ // Parse |text|, looking for pipe-delimited segment.
+ std::vector<std::u16string> segments =
+ base::SplitString(text, base::ASCIIToUTF16(kKeyNameDelimiter),
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ // SplitString() returns empty strings for zero-length segments, so given an
+ // even number of pipes, there should always be an odd number of segments.
+ // The exception is if |text| is entirely empty, in which case the returned
+ // list is also empty (rather than containing a single empty string).
+ DCHECK(segments.empty() || segments.size() % 2 == 1);
+
+ // Add text segment, alternating between non-key (no border) and key (border)
+ // formatting.
+ bool format_as_key = false;
+ for (const auto& segment : segments) {
+ AddTextSegment(segment, format_as_key);
+ format_as_key = !format_as_key;
+ }
+
+ text_ = text;
+}
+
+void SubtleNotificationView::InstructionView::AddTextSegment(
+ const std::u16string& text,
+ bool format_as_key) {
+ constexpr SkColor kForegroundColor = SK_ColorWHITE;
+
+ views::Label* label = new views::Label(text, kInstructionTextContext);
+ label->SetEnabledColor(kForegroundColor);
+ label->SetBackgroundColor(kSubtleNotificationBackgroundColor);
+
+ if (!format_as_key) {
+ AddChildView(label);
+ return;
+ }
+
+ views::View* key = new views::View;
+ auto key_name_layout = std::make_unique<views::BoxLayout>(
+ views::BoxLayout::Orientation::kHorizontal,
+ gfx::Insets(0, kKeyNamePaddingPx), 0);
+ key_name_layout->set_minimum_cross_axis_size(
+ label->GetPreferredSize().height() + kKeyNamePaddingPx * 2);
+ key->SetLayoutManager(std::move(key_name_layout));
+ key->AddChildView(label);
+ // The key name has a border around it.
+ std::unique_ptr<views::Border> border(views::CreateRoundedRectBorder(
+ kKeyNameBorderPx, kKeyNameCornerRadius, kForegroundColor));
+ key->SetBorder(std::move(border));
+ AddChildView(key);
+}
+
+BEGIN_METADATA(SubtleNotificationView, InstructionView, views::View)
+ADD_PROPERTY_METADATA(std::u16string, Text)
+END_METADATA
+
+SubtleNotificationView::SubtleNotificationView() : instruction_view_(nullptr) {
+ std::unique_ptr<views::BubbleBorder> bubble_border(new views::BubbleBorder(
+ views::BubbleBorder::NONE, views::BubbleBorder::NO_SHADOW,
+ kSubtleNotificationBackgroundColor));
+ SetBackground(std::make_unique<views::BubbleBackground>(bubble_border.get()));
+ SetBorder(std::move(bubble_border));
+
+ instruction_view_ = new InstructionView(std::u16string());
+
+ int outer_padding_horiz = kOuterPaddingHorizPx;
+ int outer_padding_vert = kOuterPaddingVertPx;
+ AddChildView(instruction_view_);
+
+ SetLayoutManager(std::make_unique<views::BoxLayout>(
+ views::BoxLayout::Orientation::kHorizontal,
+ gfx::Insets(outer_padding_vert, outer_padding_horiz), kMiddlePaddingPx));
+}
+
+SubtleNotificationView::~SubtleNotificationView() {}
+
+void SubtleNotificationView::UpdateContent(
+ const std::u16string& instruction_text) {
+ instruction_view_->SetText(instruction_text);
+ instruction_view_->SetVisible(!instruction_text.empty());
+ Layout();
+}
+
+// static
+views::Widget* SubtleNotificationView::CreatePopupWidget(
+ gfx::NativeView parent_view,
+ std::unique_ptr<SubtleNotificationView> view) {
+ // Initialize the popup.
+ views::Widget* popup = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
+ params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.parent = parent_view;
+ params.accept_events = false;
+ popup->Init(std::move(params));
+ popup->SetContentsView(std::move(view));
+ // We set layout manager to nullptr to prevent the widget from sizing its
+ // contents to the same size as itself. This prevents the widget contents from
+ // shrinking while we animate the height of the popup to give the impression
+ // that it is sliding off the top of the screen.
+ // TODO(mgiuca): This probably isn't necessary now that there is no slide
+ // animation. Remove it.
+ popup->GetRootView()->SetLayoutManager(nullptr);
+
+ return popup;
+}
+
+void SubtleNotificationView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
+ node_data->role = ax::mojom::Role::kAlert;
+ std::u16string accessible_name;
+ base::RemoveChars(instruction_view_->GetText(),
+ base::ASCIIToUTF16(kKeyNameDelimiter), &accessible_name);
+ node_data->SetName(accessible_name);
+}
+
+BEGIN_METADATA(SubtleNotificationView, views::View)
+END_METADATA
diff --git a/components/fullscreen_control/subtle_notification_view.h b/components/fullscreen_control/subtle_notification_view.h
new file mode 100644
index 0000000..b5d4365
--- /dev/null
+++ b/components/fullscreen_control/subtle_notification_view.h
@@ -0,0 +1,51 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_FULLSCREEN_CONTROL_SUBTLE_NOTIFICATION_VIEW_H_
+#define COMPONENTS_FULLSCREEN_CONTROL_SUBTLE_NOTIFICATION_VIEW_H_
+
+#include <memory>
+#include <string>
+
+#include "ui/gfx/native_widget_types.h"
+#include "ui/views/metadata/metadata_header_macros.h"
+#include "ui/views/view.h"
+
+namespace views {
+class Widget;
+}
+
+// A transient, transparent notification bubble that appears at the top of the
+// browser window to give the user a short instruction (e.g., "Press Esc to exit
+// full screen"). Unlike a full notification, a subtle notification
+// auto-dismisses after a short period of time. It also has special
+// functionality for displaying keyboard shortcuts (rendering the keys inside a
+// rounded rectangle).
+class SubtleNotificationView : public views::View {
+ public:
+ METADATA_HEADER(SubtleNotificationView);
+ SubtleNotificationView();
+ SubtleNotificationView(const SubtleNotificationView&) = delete;
+ SubtleNotificationView& operator=(const SubtleNotificationView&) = delete;
+ ~SubtleNotificationView() override;
+
+ // Display the |instruction_text| to the user. If |instruction_text| is
+ // empty hide the view.
+ void UpdateContent(const std::u16string& instruction_text);
+
+ // Creates a Widget containing a SubtleNotificationView.
+ static views::Widget* CreatePopupWidget(
+ gfx::NativeView parent_view,
+ std::unique_ptr<SubtleNotificationView> view);
+ // views::View
+ void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
+
+ private:
+ class InstructionView;
+
+ // Text displayed in the bubble, with optional keyboard keys.
+ InstructionView* instruction_view_;
+};
+
+#endif // COMPONENTS_FULLSCREEN_CONTROL_SUBTLE_NOTIFICATION_VIEW_H_
diff --git a/components/fullscreen_control_strings.grdp b/components/fullscreen_control_strings.grdp
index c608732f..e1b2985 100644
--- a/components/fullscreen_control_strings.grdp
+++ b/components/fullscreen_control_strings.grdp
@@ -3,4 +3,7 @@
<message name="IDS_EXIT_FULLSCREEN_MODE" desc="Clickable message displayed on entering full screen mode. Clicking on the link exits full screen mode.">
Exit full screen
</message>
+ <message name="IDS_FULLSCREEN_HOLD_ESC_TO_EXIT_FULLSCREEN" desc="Text displayed in the bubble to tell users how to return from fullscreen mode (where the web page occupies the entire screen) to normal mode. Please surround the name of the key (e.g. 'Esc') in pipe characters so it can be rendered as a key.">
+ Press and hold |<ph name="ACCELERATOR">$1<ex>Esc</ex></ph>| to exit full screen
+ </message>
</grit-part>
diff --git a/components/fullscreen_control_strings_grdp/IDS_FULLSCREEN_HOLD_ESC_TO_EXIT_FULLSCREEN.png.sha1 b/components/fullscreen_control_strings_grdp/IDS_FULLSCREEN_HOLD_ESC_TO_EXIT_FULLSCREEN.png.sha1
new file mode 100644
index 0000000..eabb423
--- /dev/null
+++ b/components/fullscreen_control_strings_grdp/IDS_FULLSCREEN_HOLD_ESC_TO_EXIT_FULLSCREEN.png.sha1
@@ -0,0 +1 @@
+40ceb982a00e927fce67fef094db9190f55affdf
\ No newline at end of file