1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ui/message_center/views/message_popup_view.h"
6 
7 #include "build/build_config.h"
8 #include "ui/accessibility/ax_enums.mojom.h"
9 #include "ui/accessibility/ax_node_data.h"
10 #include "ui/display/display.h"
11 #include "ui/display/screen.h"
12 #include "ui/message_center/public/cpp/message_center_constants.h"
13 #include "ui/message_center/views/message_popup_collection.h"
14 #include "ui/message_center/views/message_view.h"
15 #include "ui/message_center/views/message_view_factory.h"
16 #include "ui/views/layout/fill_layout.h"
17 #include "ui/views/widget/widget.h"
18 
19 #if defined(OS_WIN)
20 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
21 #endif
22 
23 #if defined(OS_CHROMEOS)
24 #include "ui/aura/window.h"
25 #include "ui/aura/window_targeter.h"
26 #endif
27 
28 namespace message_center {
29 
MessagePopupView(const Notification & notification,MessagePopupCollection * popup_collection)30 MessagePopupView::MessagePopupView(const Notification& notification,
31                                    MessagePopupCollection* popup_collection)
32     : message_view_(MessageViewFactory::Create(notification)),
33       popup_collection_(popup_collection),
34       a11y_feedback_on_init_(
35           notification.rich_notification_data()
36               .should_make_spoken_feedback_for_popup_updates) {
37   SetLayoutManager(std::make_unique<views::FillLayout>());
38 
39   if (!message_view_->IsManuallyExpandedOrCollapsed())
40     message_view_->SetExpanded(message_view_->IsAutoExpandingAllowed());
41   AddChildView(message_view_);
42   SetNotifyEnterExitOnChild(true);
43 }
44 
MessagePopupView(MessagePopupCollection * popup_collection)45 MessagePopupView::MessagePopupView(MessagePopupCollection* popup_collection)
46     : message_view_(nullptr),
47       popup_collection_(popup_collection),
48       a11y_feedback_on_init_(false) {
49   SetLayoutManager(std::make_unique<views::FillLayout>());
50 }
51 
~MessagePopupView()52 MessagePopupView::~MessagePopupView() {
53   popup_collection_->NotifyPopupClosed(this);
54 }
55 
UpdateContents(const Notification & notification)56 void MessagePopupView::UpdateContents(const Notification& notification) {
57   if (!IsWidgetValid())
58     return;
59   ui::AXNodeData old_data;
60   message_view_->GetAccessibleNodeData(&old_data);
61   message_view_->UpdateWithNotification(notification);
62   popup_collection_->NotifyPopupResized();
63   if (notification.rich_notification_data()
64           .should_make_spoken_feedback_for_popup_updates) {
65     ui::AXNodeData new_data;
66     message_view_->GetAccessibleNodeData(&new_data);
67 
68     const std::string& new_name =
69         new_data.GetStringAttribute(ax::mojom::StringAttribute::kName);
70     const std::string& old_name =
71         old_data.GetStringAttribute(ax::mojom::StringAttribute::kName);
72     if (new_name.empty()) {
73       new_data.SetNameFrom(ax::mojom::NameFrom::kAttributeExplicitlyEmpty);
74       return;
75     }
76 
77     if (old_name != new_name)
78       NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
79   }
80 }
81 
82 #if !defined(OS_APPLE)
GetOpacity() const83 float MessagePopupView::GetOpacity() const {
84   if (!IsWidgetValid())
85     return 0.f;
86   return GetWidget()->GetLayer()->opacity();
87 }
88 #endif
89 
SetPopupBounds(const gfx::Rect & bounds)90 void MessagePopupView::SetPopupBounds(const gfx::Rect& bounds) {
91   if (!IsWidgetValid())
92     return;
93   GetWidget()->SetBounds(bounds);
94 }
95 
SetOpacity(float opacity)96 void MessagePopupView::SetOpacity(float opacity) {
97   if (!IsWidgetValid())
98     return;
99   GetWidget()->SetOpacity(opacity);
100 }
101 
AutoCollapse()102 void MessagePopupView::AutoCollapse() {
103   if (!IsWidgetValid() || is_hovered_ ||
104       message_view_->IsManuallyExpandedOrCollapsed()) {
105     return;
106   }
107   message_view_->SetExpanded(false);
108 }
109 
Show()110 void MessagePopupView::Show() {
111   views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
112   params.z_order = ui::ZOrderLevel::kFloatingWindow;
113 #if (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_BSD)
114   // Make the widget explicitly activatable as TYPE_POPUP is not activatable by
115   // default but we need focus for the inline reply textarea.
116   params.activatable = views::Widget::InitParams::ACTIVATABLE_YES;
117   params.opacity = views::Widget::InitParams::WindowOpacity::kOpaque;
118 #else
119   params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
120 #endif
121   params.delegate = this;
122   views::Widget* widget = new views::Widget();
123   popup_collection_->ConfigureWidgetInitParamsForContainer(widget, &params);
124   widget->set_focus_on_creation(false);
125   observer_.Add(widget);
126 
127 #if defined(OS_WIN)
128   // We want to ensure that this toast always goes to the native desktop,
129   // not the Ash desktop (since there is already another toast contents view
130   // there.
131   if (!params.parent)
132     params.native_widget = new views::DesktopNativeWidgetAura(widget);
133 #endif
134 
135   widget->Init(std::move(params));
136 
137 #if defined(OS_CHROMEOS)
138   // On Chrome OS, this widget is shown in the shelf container. It means this
139   // widget would inherit the parent's window targeter (ShelfWindowTarget) by
140   // default. But it is not good for popup. So we override it with the normal
141   // WindowTargeter.
142   gfx::NativeWindow native_window = widget->GetNativeWindow();
143   native_window->SetEventTargeter(std::make_unique<aura::WindowTargeter>());
144 #endif
145 
146   widget->SetOpacity(0.0);
147   widget->ShowInactive();
148 
149   if (a11y_feedback_on_init_)
150     NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
151 }
152 
Close()153 void MessagePopupView::Close() {
154   if (!GetWidget()) {
155     DeleteDelegate();
156     return;
157   }
158 
159   if (!GetWidget()->IsClosed())
160     GetWidget()->CloseNow();
161 }
162 
OnMouseEntered(const ui::MouseEvent & event)163 void MessagePopupView::OnMouseEntered(const ui::MouseEvent& event) {
164   is_hovered_ = true;
165   popup_collection_->Update();
166 }
167 
OnMouseExited(const ui::MouseEvent & event)168 void MessagePopupView::OnMouseExited(const ui::MouseEvent& event) {
169   is_hovered_ = false;
170   popup_collection_->Update();
171 }
172 
ChildPreferredSizeChanged(views::View * child)173 void MessagePopupView::ChildPreferredSizeChanged(views::View* child) {
174   popup_collection_->NotifyPopupResized();
175 }
176 
GetAccessibleNodeData(ui::AXNodeData * node_data)177 void MessagePopupView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
178   message_view_->GetAccessibleNodeData(node_data);
179   node_data->role = ax::mojom::Role::kAlertDialog;
180 }
181 
GetClassName() const182 const char* MessagePopupView::GetClassName() const {
183   return "MessagePopupView";
184 }
185 
OnDisplayChanged()186 void MessagePopupView::OnDisplayChanged() {
187   OnWorkAreaChanged();
188 }
189 
OnWorkAreaChanged()190 void MessagePopupView::OnWorkAreaChanged() {
191   if (!IsWidgetValid())
192     return;
193 
194   gfx::NativeView native_view = GetWidget()->GetNativeView();
195   if (!native_view)
196     return;
197 
198   if (popup_collection_->RecomputeAlignment(
199           display::Screen::GetScreen()->GetDisplayNearestView(native_view))) {
200     popup_collection_->ResetBounds();
201   }
202 }
203 
OnFocus()204 void MessagePopupView::OnFocus() {
205   // This view is just a container, so advance focus to the underlying
206   // MessageView.
207   GetFocusManager()->SetFocusedView(message_view_);
208 }
209 
OnWidgetActivationChanged(views::Widget * widget,bool active)210 void MessagePopupView::OnWidgetActivationChanged(views::Widget* widget,
211                                                  bool active) {
212   is_active_ = active;
213   popup_collection_->Update();
214 }
215 
OnWidgetDestroyed(views::Widget * widget)216 void MessagePopupView::OnWidgetDestroyed(views::Widget* widget) {
217   observer_.Remove(widget);
218 }
219 
IsWidgetValid() const220 bool MessagePopupView::IsWidgetValid() const {
221   return GetWidget() && !GetWidget()->IsClosed();
222 }
223 
224 }  // namespace message_center
225