// Copyright (c) 2013 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 "ui/keyboard/content/keyboard_ui_content.h" #include "base/command_line.h" #include "base/macros.h" #include "base/values.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 "content/public/browser/site_instance.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_delegate.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_ui.h" #include "content/public/common/bindings_policy.h" #include "ui/aura/layout_manager.h" #include "ui/aura/window.h" #include "ui/base/ime/input_method.h" #include "ui/base/ime/text_input_client.h" #include "ui/keyboard/content/keyboard_constants.h" #include "ui/keyboard/keyboard_controller.h" #include "ui/keyboard/keyboard_switches.h" #include "ui/keyboard/keyboard_util.h" #include "ui/wm/core/shadow.h" namespace { // The WebContentsDelegate for the keyboard. // The delegate deletes itself when the keyboard is destroyed. class KeyboardContentsDelegate : public content::WebContentsDelegate, public content::WebContentsObserver { public: explicit KeyboardContentsDelegate(keyboard::KeyboardUIContent* ui) : ui_(ui) {} ~KeyboardContentsDelegate() override {} private: // Overridden from content::WebContentsDelegate: content::WebContents* OpenURLFromTab( content::WebContents* source, const content::OpenURLParams& params) override { source->GetController().LoadURL( params.url, params.referrer, params.transition, params.extra_headers); Observe(source); return source; } bool CanDragEnter(content::WebContents* source, const content::DropData& data, blink::WebDragOperationsMask operations_allowed) override { return false; } bool ShouldCreateWebContents( content::WebContents* web_contents, int route_id, int main_frame_route_id, int main_frame_widget_route_id, WindowContainerType window_container_type, const std::string& frame_name, const GURL& target_url, const std::string& partition_id, content::SessionStorageNamespace* session_storage_namespace) override { return false; } bool IsPopupOrPanel(const content::WebContents* source) const override { return true; } void MoveContents(content::WebContents* source, const gfx::Rect& pos) override { aura::Window* keyboard = ui_->GetKeyboardWindow(); // keyboard window must have been added to keyboard container window at this // point. Otherwise, wrong keyboard bounds is used and may cause problem as // described in crbug.com/367788. DCHECK(keyboard->parent()); // keyboard window bounds may not set to |pos| after this call. If keyboard // is in FULL_WIDTH mode, only the height of keyboard window will be // changed. keyboard->SetBounds(pos); } // Overridden from content::WebContentsDelegate: void RequestMediaAccessPermission( content::WebContents* web_contents, const content::MediaStreamRequest& request, const content::MediaResponseCallback& callback) override { ui_->RequestAudioInput(web_contents, request, callback); } // Overridden from content::WebContentsObserver: void WebContentsDestroyed() override { delete this; } keyboard::KeyboardUIContent* ui_; DISALLOW_COPY_AND_ASSIGN(KeyboardContentsDelegate); }; } // namespace namespace keyboard { class WindowBoundsChangeObserver : public aura::WindowObserver { public: explicit WindowBoundsChangeObserver(KeyboardUIContent* ui) : ui_(ui) {} ~WindowBoundsChangeObserver() override {} void AddObservedWindow(aura::Window* window) { if (!window->HasObserver(this)) { window->AddObserver(this); observed_windows_.insert(window); } } void RemoveAllObservedWindows() { for (std::set::iterator it = observed_windows_.begin(); it != observed_windows_.end(); ++it) (*it)->RemoveObserver(this); observed_windows_.clear(); } private: void OnWindowBoundsChanged(aura::Window* window, const gfx::Rect& old_bounds, const gfx::Rect& new_bounds) override { ui_->UpdateInsetsForWindow(window); } void OnWindowDestroyed(aura::Window* window) override { if (window->HasObserver(this)) window->RemoveObserver(this); observed_windows_.erase(window); } KeyboardUIContent* ui_; std::set observed_windows_; DISALLOW_COPY_AND_ASSIGN(WindowBoundsChangeObserver); }; KeyboardUIContent::KeyboardUIContent(content::BrowserContext* context) : browser_context_(context), default_url_(kKeyboardURL), window_bounds_observer_(new WindowBoundsChangeObserver(this)) { } KeyboardUIContent::~KeyboardUIContent() { ResetInsets(); } void KeyboardUIContent::LoadSystemKeyboard() { DCHECK(keyboard_contents_); if (keyboard_contents_->GetURL() != default_url_) { // TODO(bshe): The height of system virtual keyboard and IME virtual // keyboard may different. The height needs to be restored too. LoadContents(default_url_); } } void KeyboardUIContent::UpdateInsetsForWindow(aura::Window* window) { aura::Window* keyboard_container = keyboard_controller()->GetContainerWindow(); if (!ShouldWindowOverscroll(window)) return; scoped_ptr widgets( content::RenderWidgetHost::GetRenderWidgetHosts()); while (content::RenderWidgetHost* widget = widgets->GetNextHost()) { content::RenderWidgetHostView* view = widget->GetView(); if (view && window->Contains(view->GetNativeView())) { gfx::Rect window_bounds = view->GetNativeView()->GetBoundsInScreen(); gfx::Rect intersect = gfx::IntersectRects(window_bounds, keyboard_container->bounds()); int overlap = ShouldEnableInsets(window) ? intersect.height() : 0; if (overlap > 0 && overlap < window_bounds.height()) view->SetInsets(gfx::Insets(0, 0, overlap, 0)); else view->SetInsets(gfx::Insets()); return; } } } aura::Window* KeyboardUIContent::GetKeyboardWindow() { if (!keyboard_contents_) { content::BrowserContext* context = browser_context(); keyboard_contents_.reset(content::WebContents::Create( content::WebContents::CreateParams(context, content::SiteInstance::CreateForURL(context, GetVirtualKeyboardUrl())))); keyboard_contents_->SetDelegate(new KeyboardContentsDelegate(this)); SetupWebContents(keyboard_contents_.get()); LoadContents(GetVirtualKeyboardUrl()); keyboard_contents_->GetNativeView()->AddObserver(this); } return keyboard_contents_->GetNativeView(); } bool KeyboardUIContent::HasKeyboardWindow() const { return !!keyboard_contents_; } bool KeyboardUIContent::ShouldWindowOverscroll(aura::Window* window) const { return true; } void KeyboardUIContent::ReloadKeyboardIfNeeded() { DCHECK(keyboard_contents_); if (keyboard_contents_->GetURL() != GetVirtualKeyboardUrl()) { if (keyboard_contents_->GetURL().GetOrigin() != GetVirtualKeyboardUrl().GetOrigin()) { // Sets keyboard window rectangle to 0 and close current page before // navigate to a keyboard in a different extension. This keeps the UX the // same as Android. Note we need to explicitly close current page as it // might try to resize keyboard window in javascript on a resize event. GetKeyboardWindow()->SetBounds(gfx::Rect()); keyboard_contents_->ClosePage(); keyboard_controller()->SetKeyboardMode(FULL_WIDTH); } LoadContents(GetVirtualKeyboardUrl()); } } void KeyboardUIContent::InitInsets(const gfx::Rect& new_bounds) { // 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. scoped_ptr widgets( content::RenderWidgetHost::GetRenderWidgetHosts()); while (content::RenderWidgetHost* widget = widgets->GetNextHost()) { content::RenderWidgetHostView* view = widget->GetView(); // Can be NULL, e.g. if the RenderWidget is being destroyed or // the render process crashed. if (view) { aura::Window* window = view->GetNativeView(); if (ShouldWindowOverscroll(window)) { gfx::Rect window_bounds = window->GetBoundsInScreen(); gfx::Rect intersect = gfx::IntersectRects(window_bounds, new_bounds); int overlap = intersect.height(); if (overlap > 0 && overlap < window_bounds.height()) view->SetInsets(gfx::Insets(0, 0, overlap, 0)); else view->SetInsets(gfx::Insets()); AddBoundsChangedObserver(window); } } } } void KeyboardUIContent::ResetInsets() { const gfx::Insets insets; scoped_ptr widgets( content::RenderWidgetHost::GetRenderWidgetHosts()); while (content::RenderWidgetHost* widget = widgets->GetNextHost()) { content::RenderWidgetHostView* view = widget->GetView(); if (view) view->SetInsets(insets); } window_bounds_observer_->RemoveAllObservedWindows(); } void KeyboardUIContent::SetupWebContents(content::WebContents* contents) { } void KeyboardUIContent::OnWindowBoundsChanged(aura::Window* window, const gfx::Rect& old_bounds, const gfx::Rect& new_bounds) { if (!shadow_) { shadow_.reset(new wm::Shadow()); shadow_->Init(wm::Shadow::STYLE_ACTIVE); shadow_->layer()->SetVisible(true); DCHECK(keyboard_contents_->GetNativeView()->parent()); keyboard_contents_->GetNativeView()->parent()->layer()->Add( shadow_->layer()); } shadow_->SetContentBounds(new_bounds); } void KeyboardUIContent::OnWindowDestroyed(aura::Window* window) { window->RemoveObserver(this); } const aura::Window* KeyboardUIContent::GetKeyboardRootWindow() const { if (!keyboard_contents_) { return nullptr; } return keyboard_contents_->GetNativeView()->GetRootWindow(); } void KeyboardUIContent::LoadContents(const GURL& url) { if (keyboard_contents_) { content::OpenURLParams params( url, content::Referrer(), SINGLETON_TAB, ui::PAGE_TRANSITION_AUTO_TOPLEVEL, false); keyboard_contents_->OpenURL(params); } } const GURL& KeyboardUIContent::GetVirtualKeyboardUrl() { if (keyboard::IsInputViewEnabled()) { const GURL& override_url = GetOverrideContentUrl(); return override_url.is_valid() ? override_url : default_url_; } else { return default_url_; } } bool KeyboardUIContent::ShouldEnableInsets(aura::Window* window) { aura::Window* keyboard_window = GetKeyboardWindow(); return (keyboard_window->GetRootWindow() == window->GetRootWindow() && keyboard::IsKeyboardOverscrollEnabled() && keyboard_window->IsVisible() && keyboard_controller()->keyboard_visible()); } void KeyboardUIContent::AddBoundsChangedObserver(aura::Window* window) { aura::Window* target_window = window ? window->GetToplevelWindow() : nullptr; if (target_window) window_bounds_observer_->AddObservedWindow(target_window); } } // namespace keyboard