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