|  | // Copyright 2018 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 "chrome/browser/ui/ash/keyboard/chrome_keyboard_bounds_observer.h" | 
|  |  | 
|  | #include "ash/public/cpp/shell_window_ids.h" | 
|  | #include "ash/root_window_controller.h" | 
|  | #include "chrome/browser/apps/platform_apps/app_window_registry_util.h" | 
|  | #include "content/public/browser/render_widget_host.h" | 
|  | #include "content/public/browser/render_widget_host_iterator.h" | 
|  | #include "content/public/browser/render_widget_host_view.h" | 
|  | #include "extensions/browser/app_window/app_window.h" | 
|  | #include "ui/aura/window.h" | 
|  | #include "ui/base/ime/chromeos/ime_bridge.h" | 
|  | #include "ui/base/ime/input_method.h" | 
|  | #include "ui/base/ime/text_input_client.h" | 
|  | #include "ui/base/ui_base_features.h" | 
|  | #include "ui/display/display.h" | 
|  | #include "ui/display/screen.h" | 
|  | #include "ui/gfx/geometry/insets.h" | 
|  | #include "ui/views/view.h" | 
|  | #include "ui/views/widget/widget.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Returns the RenderWidgetHostView contained by |window|. | 
|  | content::RenderWidgetHostView* GetHostViewForWindow(aura::Window* window) { | 
|  | std::unique_ptr<content::RenderWidgetHostIterator> hosts( | 
|  | content::RenderWidgetHost::GetRenderWidgetHosts()); | 
|  | while (content::RenderWidgetHost* host = hosts->GetNextHost()) { | 
|  | content::RenderWidgetHostView* view = host->GetView(); | 
|  | if (view && window->Contains(view->GetNativeView())) | 
|  | return view; | 
|  | } | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | ui::InputMethod* GetCurrentInputMethod() { | 
|  | ui::IMEBridge* bridge = ui::IMEBridge::Get(); | 
|  | if (bridge && bridge->GetInputContextHandler()) | 
|  | return bridge->GetInputContextHandler()->GetInputMethod(); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | ChromeKeyboardBoundsObserver::ChromeKeyboardBoundsObserver( | 
|  | aura::Window* keyboard_window) | 
|  | : keyboard_window_(keyboard_window) { | 
|  | DCHECK(keyboard_window_); | 
|  | ChromeKeyboardControllerClient::Get()->AddObserver(this); | 
|  | } | 
|  |  | 
|  | ChromeKeyboardBoundsObserver::~ChromeKeyboardBoundsObserver() { | 
|  | UpdateOccludedBounds(gfx::Rect()); | 
|  |  | 
|  | RemoveAllObservedWindows(); | 
|  |  | 
|  | ChromeKeyboardControllerClient::Get()->RemoveObserver(this); | 
|  | CHECK(!views::WidgetObserver::IsInObserverList()); | 
|  | } | 
|  |  | 
|  | void ChromeKeyboardBoundsObserver::OnKeyboardOccludedBoundsChanged( | 
|  | const gfx::Rect& screen_bounds) { | 
|  | DVLOG(1) << "OnKeyboardOccludedBoundsChanged: " << screen_bounds.ToString(); | 
|  | UpdateOccludedBounds( | 
|  | ChromeKeyboardControllerClient::Get()->IsKeyboardOverscrollEnabled() | 
|  | ? screen_bounds | 
|  | : gfx::Rect()); | 
|  | } | 
|  |  | 
|  | void ChromeKeyboardBoundsObserver::UpdateOccludedBounds( | 
|  | const gfx::Rect& screen_bounds) { | 
|  | DVLOG(1) << "UpdateOccludedBounds: " << screen_bounds.ToString(); | 
|  | occluded_bounds_in_screen_ = screen_bounds; | 
|  |  | 
|  | std::unique_ptr<content::RenderWidgetHostIterator> hosts( | 
|  | content::RenderWidgetHost::GetRenderWidgetHosts()); | 
|  |  | 
|  | // If the keyboard is hidden or floating then reset the insets for all | 
|  | // RenderWidgetHosts and remove observers. | 
|  | if (occluded_bounds_in_screen_.IsEmpty()) { | 
|  | while (content::RenderWidgetHost* host = hosts->GetNextHost()) { | 
|  | content::RenderWidgetHostView* view = host->GetView(); | 
|  | if (view) | 
|  | view->SetInsets(gfx::Insets()); | 
|  | } | 
|  | RemoveAllObservedWindows(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Adjust the height of the viewport for visible windows on the primary | 
|  | // display. TODO(kevers): Add EnvObserver to properly initialize insets if a | 
|  | // window is created while the keyboard is visible. | 
|  | while (content::RenderWidgetHost* host = hosts->GetNextHost()) { | 
|  | content::RenderWidgetHostView* view = host->GetView(); | 
|  | // Can be null, e.g. if the RenderWidget is being destroyed or | 
|  | // the render process crashed. | 
|  | if (!view) | 
|  | continue; | 
|  |  | 
|  | aura::Window* window = view->GetNativeView(); | 
|  | // Added while we determine if RenderWidgetHostViewChildFrame can be | 
|  | // changed to always return a non-null value: https://crbug.com/644726. | 
|  | // If we cannot guarantee a non-null value, then this may need to stay. | 
|  | if (!window) | 
|  | continue; | 
|  |  | 
|  | if (!ShouldWindowOverscroll(window)) | 
|  | continue; | 
|  |  | 
|  | UpdateInsets(window, view); | 
|  | AddObservedWindow(window); | 
|  | } | 
|  |  | 
|  | // Window reshape can race with the IME trying to keep the text input caret | 
|  | // visible. Do this here because the widget bounds change happens before the | 
|  | // occluded bounds are updated. https://crbug.com/937722 | 
|  | ui::InputMethod* ime = GetCurrentInputMethod(); | 
|  | if (ime && ime->GetTextInputClient()) | 
|  | ime->GetTextInputClient()->EnsureCaretNotInRect(occluded_bounds_in_screen_); | 
|  | } | 
|  |  | 
|  | void ChromeKeyboardBoundsObserver::AddObservedWindow(aura::Window* window) { | 
|  | // Only observe top level widget. | 
|  | views::Widget* widget = | 
|  | views::Widget::GetWidgetForNativeView(window->GetToplevelWindow()); | 
|  | if (!widget || widget->HasObserver(this)) | 
|  | return; | 
|  |  | 
|  | widget->AddObserver(this); | 
|  | observed_widgets_.insert(widget); | 
|  | } | 
|  |  | 
|  | void ChromeKeyboardBoundsObserver::RemoveAllObservedWindows() { | 
|  | for (views::Widget* widget : observed_widgets_) | 
|  | widget->RemoveObserver(this); | 
|  | observed_widgets_.clear(); | 
|  | } | 
|  |  | 
|  | void ChromeKeyboardBoundsObserver::OnWidgetBoundsChanged( | 
|  | views::Widget* widget, | 
|  | const gfx::Rect& new_bounds) { | 
|  | DVLOG(1) << "OnWidgetBoundsChanged: " << widget->GetName() << " " | 
|  | << new_bounds.ToString(); | 
|  |  | 
|  | aura::Window* window = widget->GetNativeView(); | 
|  | if (!ShouldWindowOverscroll(window)) | 
|  | return; | 
|  |  | 
|  | content::RenderWidgetHostView* host_view = GetHostViewForWindow(window); | 
|  | if (!host_view) | 
|  | return;  // Transition edge case | 
|  |  | 
|  | UpdateInsets(window, host_view); | 
|  | } | 
|  |  | 
|  | void ChromeKeyboardBoundsObserver::OnWidgetDestroying(views::Widget* widget) { | 
|  | if (widget->HasObserver(this)) | 
|  | widget->RemoveObserver(this); | 
|  | observed_widgets_.erase(widget); | 
|  | } | 
|  |  | 
|  | void ChromeKeyboardBoundsObserver::UpdateInsets( | 
|  | aura::Window* window, | 
|  | content::RenderWidgetHostView* view) { | 
|  | gfx::Rect view_bounds_in_screen = view->GetViewBounds(); | 
|  | if (!ShouldEnableInsets(window)) { | 
|  | DVLOG(2) << "ResetInsets: " << window->GetName() | 
|  | << " Bounds: " << view_bounds_in_screen.ToString(); | 
|  | view->SetInsets(gfx::Insets()); | 
|  | return; | 
|  | } | 
|  | gfx::Rect intersect = | 
|  | gfx::IntersectRects(view_bounds_in_screen, occluded_bounds_in_screen_); | 
|  | int overlap = intersect.height(); | 
|  | DVLOG(2) << "SetInsets: " << window->GetName() | 
|  | << " Bounds: " << view_bounds_in_screen.ToString() | 
|  | << " Overlap: " << overlap; | 
|  | if (overlap > 0 && overlap < view_bounds_in_screen.height()) | 
|  | view->SetInsets(gfx::Insets(0, 0, overlap, 0)); | 
|  | else | 
|  | view->SetInsets(gfx::Insets()); | 
|  | } | 
|  |  | 
|  | bool ChromeKeyboardBoundsObserver::ShouldWindowOverscroll( | 
|  | aura::Window* window) { | 
|  | // The virtual keyboard should not overscroll. | 
|  | if (window->GetToplevelWindow() == keyboard_window_->GetToplevelWindow()) | 
|  | return false; | 
|  |  | 
|  | // IME windows should not overscroll. | 
|  | extensions::AppWindow* app_window = | 
|  | AppWindowRegistryUtil::GetAppWindowForNativeWindowAnyProfile( | 
|  | window->GetToplevelWindow()); | 
|  | if (app_window && app_window->is_ime_window()) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ChromeKeyboardBoundsObserver::ShouldEnableInsets(aura::Window* window) { | 
|  | if (!keyboard_window_->IsVisible() || | 
|  | !ChromeKeyboardControllerClient::Get()->IsKeyboardOverscrollEnabled()) { | 
|  | return false; | 
|  | } | 
|  | const auto* screen = display::Screen::GetScreen(); | 
|  | return screen->GetDisplayNearestWindow(window).id() == | 
|  | screen->GetDisplayNearestWindow(keyboard_window_).id(); | 
|  | } |