blob: 20fca8dfd1c92c7778a73d413f806e195931b45b [file] [log] [blame]
// Copyright 2016 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/input_method/input_method_engine.h"
#include "base/stl_util.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/url_constants.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/common/url_constants.h"
#include "extensions/common/constants.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/ime/composition_text.h"
#include "ui/base/ime/ime_bridge.h"
#include "ui/base/ime/ime_input_context_handler_interface.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"
#include "url/gurl.h"
namespace {
const char kErrorFollowCursorWindowExists[] =
"A follow cursor IME window exists.";
const char kErrorNoInputFocus[] =
"The follow cursor IME window cannot be created without an input focus.";
const char kErrorReachMaxWindowCount[] =
"Cannot create more than 5 normal IME windows.";
const int kMaxNormalWindowCount = 5;
} // namespace
namespace input_method {
InputMethodEngine::InputMethodEngine() : follow_cursor_window_(nullptr) {}
InputMethodEngine::~InputMethodEngine() {
// Removes the listeners for OnWindowDestroyed.
if (follow_cursor_window_)
follow_cursor_window_->RemoveObserver(this);
for (auto* window : normal_windows_)
window->RemoveObserver(this);
CloseImeWindows();
}
bool InputMethodEngine::IsActive() const {
return true;
}
std::string InputMethodEngine::GetExtensionId() const {
return extension_id_;
}
int InputMethodEngine::CreateImeWindow(
const extensions::Extension* extension,
content::RenderFrameHost* render_frame_host,
const std::string& url,
ui::ImeWindow::Mode mode,
const gfx::Rect& bounds,
std::string* error) {
if (mode == ui::ImeWindow::FOLLOW_CURSOR) {
if (follow_cursor_window_) {
*error = kErrorFollowCursorWindowExists;
return 0;
}
if (current_input_type_ == ui::TEXT_INPUT_TYPE_NONE) {
*error = kErrorNoInputFocus;
return 0;
}
}
if (mode == ui::ImeWindow::NORMAL &&
normal_windows_.size() >= kMaxNormalWindowCount) {
*error = kErrorReachMaxWindowCount;
return 0;
}
// ui::ImeWindow manages its own lifetime.
ui::ImeWindow* ime_window = new ui::ImeWindow(
profile_, extension, render_frame_host, url, mode, bounds);
ime_window->AddObserver(this);
ime_window->Show();
if (mode == ui::ImeWindow::FOLLOW_CURSOR) {
follow_cursor_window_ = ime_window;
ime_window->FollowCursor(current_cursor_bounds_);
} else {
normal_windows_.push_back(ime_window);
}
return ime_window->GetFrameId();
}
void InputMethodEngine::ShowImeWindow(int window_id) {
ui::ImeWindow* ime_window = FindWindowById(window_id);
if (ime_window)
ime_window->Show();
}
void InputMethodEngine::HideImeWindow(int window_id) {
ui::ImeWindow* ime_window = FindWindowById(window_id);
if (ime_window)
ime_window->Hide();
}
void InputMethodEngine::CloseImeWindows() {
if (follow_cursor_window_)
follow_cursor_window_->Close();
for (auto* window : normal_windows_)
window->Close();
normal_windows_.clear();
}
void InputMethodEngine::FocusOut() {
InputMethodEngineBase::FocusOut();
if (follow_cursor_window_)
follow_cursor_window_->Hide();
}
void InputMethodEngine::SetCompositionBounds(
const std::vector<gfx::Rect>& bounds) {
InputMethodEngineBase::SetCompositionBounds(bounds);
if (!bounds.empty()) {
current_cursor_bounds_ = bounds[0];
if (follow_cursor_window_)
follow_cursor_window_->FollowCursor(current_cursor_bounds_);
}
}
void InputMethodEngine::UpdateComposition(
const ui::CompositionText& composition_text,
uint32_t cursor_pos,
bool is_visible) {
composition_ = composition_text;
// Use a thin underline with text color by default.
if (composition_.ime_text_spans.empty()) {
composition_.ime_text_spans.push_back(ui::ImeTextSpan(
ui::ImeTextSpan::Type::kComposition, 0, composition_.text.length(),
ui::ImeTextSpan::Thickness::kThin, SK_ColorTRANSPARENT));
}
ui::IMEInputContextHandlerInterface* input_context =
ui::IMEBridge::Get()->GetInputContextHandler();
// If the IME extension is handling key event, hold the composition text
// until the key event is handled.
if (input_context && !handling_key_event_) {
input_context->UpdateCompositionText(composition_, cursor_pos, is_visible);
composition_ = ui::CompositionText();
} else {
composition_changed_ = true;
}
}
void InputMethodEngine::CommitTextToInputContext(int context_id,
const std::string& text) {
// Append the text to the buffer, as it allows committing text multiple times
// when processing a key event.
text_ += text;
ui::IMEInputContextHandlerInterface* input_context =
ui::IMEBridge::Get()->GetInputContextHandler();
// If the IME extension is handling key event, hold the text until the key
// event is handled.
if (input_context && !handling_key_event_) {
input_context->CommitText(text_);
text_ = "";
} else {
commit_text_changed_ = true;
}
}
void InputMethodEngine::OnWindowDestroyed(ui::ImeWindow* ime_window) {
if (ime_window == follow_cursor_window_) {
follow_cursor_window_ = nullptr;
} else {
auto it = std::find(
normal_windows_.begin(), normal_windows_.end(), ime_window);
if (it != normal_windows_.end())
normal_windows_.erase(it);
}
}
ui::ImeWindow* InputMethodEngine::FindWindowById(int window_id) const {
if (follow_cursor_window_ &&
follow_cursor_window_->GetFrameId() == window_id) {
return follow_cursor_window_;
}
for (auto* ime_window : normal_windows_) {
if (ime_window->GetFrameId() == window_id)
return ime_window;
}
return nullptr;
}
bool InputMethodEngine::SendKeyEvent(ui::KeyEvent* event,
const std::string& code) {
DCHECK(event);
// input.ime.sendKeyEvents API is only allowed to work on text fields.
if (current_input_type_ == ui::TEXT_INPUT_TYPE_NONE)
return false;
if (event->key_code() == ui::VKEY_UNKNOWN)
event->set_key_code(ui::DomCodeToUsLayoutKeyboardCode(event->code()));
ui::IMEInputContextHandlerInterface* input_context =
ui::IMEBridge::Get()->GetInputContextHandler();
if (!input_context)
return false;
// ENTER et al. keys are allowed to work only on http:, https: etc.
if (!IsValidKeyForAllPages(event)) {
if (IsSpecialPage(input_context->GetInputMethod()))
return false;
}
input_context->SendKeyEvent(event);
return true;
}
bool InputMethodEngine::IsSpecialPage(ui::InputMethod* input_method) {
Browser* browser = chrome::FindLastActive();
DCHECK(browser);
content::WebContents* web_contents =
browser->tab_strip_model()->GetActiveWebContents();
if (!web_contents)
return true;
// If the input method get from last active browser is different from the
// input method get from input context, it means we can't get the real url of
// the input view, treats |url| as special page anyway for security concerns.
if (browser->window()->GetNativeWindow()->GetHost()->GetInputMethod() !=
input_method)
return true;
GURL url = web_contents->GetLastCommittedURL();
// If we can't determine the last committed url, treat it as special.
if (!url.is_valid())
return true;
// Checks if the last committed url has the whitelisted sheme.
std::vector<const char*> whitelist_schemes{url::kFtpScheme, url::kHttpScheme,
url::kHttpsScheme};
for (auto* scheme : whitelist_schemes) {
if (url.SchemeIs(scheme))
return false;
}
// Checks if the last committed url is whitelisted url.
url::Origin origin = url::Origin::Create(url);
std::vector<GURL> whitelist_urls{GURL(url::kAboutBlankURL),
GURL(chrome::kChromeUINewTabURL),
GURL(chrome::kChromeSearchLocalNtpUrl)};
for (const GURL& whitelist_url : whitelist_urls) {
if (url::Origin::Create(whitelist_url).IsSameOriginWith(origin))
return false;
}
return true;
}
bool InputMethodEngine::IsValidKeyForAllPages(ui::KeyEvent* ui_event) {
// Whitelists all character keys except for Enter and Tab keys.
std::vector<ui::KeyboardCode> invalid_character_keycodes{ui::VKEY_TAB,
ui::VKEY_RETURN};
if (ui_event->GetDomKey().IsCharacter() && !ui_event->IsControlDown() &&
!ui_event->IsCommandDown()) {
return !base::ContainsValue(invalid_character_keycodes,
ui_event->key_code());
}
// Whitelists Backspace key and arrow keys.
std::vector<ui::KeyboardCode> whitelist_keycodes{
ui::VKEY_BACK, ui::VKEY_LEFT, ui::VKEY_RIGHT, ui::VKEY_UP, ui::VKEY_DOWN};
return base::ContainsValue(whitelist_keycodes, ui_event->key_code());
}
} // namespace input_method