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 "chrome/browser/ui/ash/keyboard/chrome_keyboard_bounds_observer.h"
6
7 #include "ash/public/cpp/shell_window_ids.h"
8 #include "ash/root_window_controller.h"
9 #include "chrome/browser/apps/platform_apps/app_window_registry_util.h"
10 #include "content/public/browser/render_widget_host.h"
11 #include "content/public/browser/render_widget_host_iterator.h"
12 #include "content/public/browser/render_widget_host_view.h"
13 #include "extensions/browser/app_window/app_window.h"
14 #include "ui/aura/window.h"
15 #include "ui/base/ime/chromeos/ime_bridge.h"
16 #include "ui/base/ime/input_method.h"
17 #include "ui/base/ime/text_input_client.h"
18 #include "ui/base/ui_base_features.h"
19 #include "ui/display/display.h"
20 #include "ui/display/screen.h"
21 #include "ui/gfx/geometry/insets.h"
22 #include "ui/views/view.h"
23 #include "ui/views/widget/widget.h"
24
25 namespace {
26
27 // Returns the RenderWidgetHostView contained by |window|.
GetHostViewForWindow(aura::Window * window)28 content::RenderWidgetHostView* GetHostViewForWindow(aura::Window* window) {
29 std::unique_ptr<content::RenderWidgetHostIterator> hosts(
30 content::RenderWidgetHost::GetRenderWidgetHosts());
31 while (content::RenderWidgetHost* host = hosts->GetNextHost()) {
32 content::RenderWidgetHostView* view = host->GetView();
33 if (view && window->Contains(view->GetNativeView()))
34 return view;
35 }
36 return nullptr;
37 }
38
GetCurrentInputMethod()39 ui::InputMethod* GetCurrentInputMethod() {
40 ui::IMEBridge* bridge = ui::IMEBridge::Get();
41 if (bridge && bridge->GetInputContextHandler())
42 return bridge->GetInputContextHandler()->GetInputMethod();
43 return nullptr;
44 }
45
46 } // namespace
47
ChromeKeyboardBoundsObserver(aura::Window * keyboard_window)48 ChromeKeyboardBoundsObserver::ChromeKeyboardBoundsObserver(
49 aura::Window* keyboard_window)
50 : keyboard_window_(keyboard_window) {
51 DCHECK(keyboard_window_);
52 ChromeKeyboardControllerClient::Get()->AddObserver(this);
53 }
54
~ChromeKeyboardBoundsObserver()55 ChromeKeyboardBoundsObserver::~ChromeKeyboardBoundsObserver() {
56 UpdateOccludedBounds(gfx::Rect());
57
58 RemoveAllObservedWindows();
59
60 ChromeKeyboardControllerClient::Get()->RemoveObserver(this);
61 CHECK(!views::WidgetObserver::IsInObserverList());
62 }
63
OnKeyboardOccludedBoundsChanged(const gfx::Rect & screen_bounds)64 void ChromeKeyboardBoundsObserver::OnKeyboardOccludedBoundsChanged(
65 const gfx::Rect& screen_bounds) {
66 DVLOG(1) << "OnKeyboardOccludedBoundsChanged: " << screen_bounds.ToString();
67 UpdateOccludedBounds(
68 ChromeKeyboardControllerClient::Get()->IsKeyboardOverscrollEnabled()
69 ? screen_bounds
70 : gfx::Rect());
71 }
72
UpdateOccludedBounds(const gfx::Rect & screen_bounds)73 void ChromeKeyboardBoundsObserver::UpdateOccludedBounds(
74 const gfx::Rect& screen_bounds) {
75 DVLOG(1) << "UpdateOccludedBounds: " << screen_bounds.ToString();
76 occluded_bounds_in_screen_ = screen_bounds;
77
78 std::unique_ptr<content::RenderWidgetHostIterator> hosts(
79 content::RenderWidgetHost::GetRenderWidgetHosts());
80
81 // If the keyboard is hidden or floating then reset the insets for all
82 // RenderWidgetHosts and remove observers.
83 if (occluded_bounds_in_screen_.IsEmpty()) {
84 while (content::RenderWidgetHost* host = hosts->GetNextHost()) {
85 content::RenderWidgetHostView* view = host->GetView();
86 if (view)
87 view->SetInsets(gfx::Insets());
88 }
89 RemoveAllObservedWindows();
90 return;
91 }
92
93 // Adjust the height of the viewport for visible windows on the primary
94 // display. TODO(kevers): Add EnvObserver to properly initialize insets if a
95 // window is created while the keyboard is visible.
96 while (content::RenderWidgetHost* host = hosts->GetNextHost()) {
97 content::RenderWidgetHostView* view = host->GetView();
98 // Can be null, e.g. if the RenderWidget is being destroyed or
99 // the render process crashed.
100 if (!view)
101 continue;
102
103 aura::Window* window = view->GetNativeView();
104 // Added while we determine if RenderWidgetHostViewChildFrame can be
105 // changed to always return a non-null value: https://crbug.com/644726.
106 // If we cannot guarantee a non-null value, then this may need to stay.
107 if (!window)
108 continue;
109
110 if (!ShouldWindowOverscroll(window))
111 continue;
112
113 UpdateInsets(window, view);
114 AddObservedWindow(window);
115 }
116
117 // Window reshape can race with the IME trying to keep the text input caret
118 // visible. Do this here because the widget bounds change happens before the
119 // occluded bounds are updated. https://crbug.com/937722
120 ui::InputMethod* ime = GetCurrentInputMethod();
121 if (ime && ime->GetTextInputClient())
122 ime->GetTextInputClient()->EnsureCaretNotInRect(occluded_bounds_in_screen_);
123 }
124
AddObservedWindow(aura::Window * window)125 void ChromeKeyboardBoundsObserver::AddObservedWindow(aura::Window* window) {
126 // Only observe top level widget.
127 views::Widget* widget =
128 views::Widget::GetWidgetForNativeView(window->GetToplevelWindow());
129 if (!widget || widget->HasObserver(this))
130 return;
131
132 widget->AddObserver(this);
133 observed_widgets_.insert(widget);
134 }
135
RemoveAllObservedWindows()136 void ChromeKeyboardBoundsObserver::RemoveAllObservedWindows() {
137 for (views::Widget* widget : observed_widgets_)
138 widget->RemoveObserver(this);
139 observed_widgets_.clear();
140 }
141
OnWidgetBoundsChanged(views::Widget * widget,const gfx::Rect & new_bounds)142 void ChromeKeyboardBoundsObserver::OnWidgetBoundsChanged(
143 views::Widget* widget,
144 const gfx::Rect& new_bounds) {
145 DVLOG(1) << "OnWidgetBoundsChanged: " << widget->GetName() << " "
146 << new_bounds.ToString();
147
148 aura::Window* window = widget->GetNativeView();
149 if (!ShouldWindowOverscroll(window))
150 return;
151
152 content::RenderWidgetHostView* host_view = GetHostViewForWindow(window);
153 if (!host_view)
154 return; // Transition edge case
155
156 UpdateInsets(window, host_view);
157 }
158
OnWidgetDestroying(views::Widget * widget)159 void ChromeKeyboardBoundsObserver::OnWidgetDestroying(views::Widget* widget) {
160 if (widget->HasObserver(this))
161 widget->RemoveObserver(this);
162 observed_widgets_.erase(widget);
163 }
164
UpdateInsets(aura::Window * window,content::RenderWidgetHostView * view)165 void ChromeKeyboardBoundsObserver::UpdateInsets(
166 aura::Window* window,
167 content::RenderWidgetHostView* view) {
168 gfx::Rect view_bounds_in_screen = view->GetViewBounds();
169 if (!ShouldEnableInsets(window)) {
170 DVLOG(2) << "ResetInsets: " << window->GetName()
171 << " Bounds: " << view_bounds_in_screen.ToString();
172 view->SetInsets(gfx::Insets());
173 return;
174 }
175 gfx::Rect intersect =
176 gfx::IntersectRects(view_bounds_in_screen, occluded_bounds_in_screen_);
177 int overlap = intersect.height();
178 DVLOG(2) << "SetInsets: " << window->GetName()
179 << " Bounds: " << view_bounds_in_screen.ToString()
180 << " Overlap: " << overlap;
181 if (overlap > 0 && overlap < view_bounds_in_screen.height())
182 view->SetInsets(gfx::Insets(0, 0, overlap, 0));
183 else
184 view->SetInsets(gfx::Insets());
185 }
186
ShouldWindowOverscroll(aura::Window * window)187 bool ChromeKeyboardBoundsObserver::ShouldWindowOverscroll(
188 aura::Window* window) {
189 // The virtual keyboard should not overscroll.
190 if (window->GetToplevelWindow() == keyboard_window_->GetToplevelWindow())
191 return false;
192
193 // IME windows should not overscroll.
194 extensions::AppWindow* app_window =
195 AppWindowRegistryUtil::GetAppWindowForNativeWindowAnyProfile(
196 window->GetToplevelWindow());
197 if (app_window && app_window->is_ime_window())
198 return false;
199
200 return true;
201 }
202
ShouldEnableInsets(aura::Window * window)203 bool ChromeKeyboardBoundsObserver::ShouldEnableInsets(aura::Window* window) {
204 if (!keyboard_window_->IsVisible() ||
205 !ChromeKeyboardControllerClient::Get()->IsKeyboardOverscrollEnabled()) {
206 return false;
207 }
208 const auto* screen = display::Screen::GetScreen();
209 return screen->GetDisplayNearestWindow(window).id() ==
210 screen->GetDisplayNearestWindow(keyboard_window_).id();
211 }
212