diff options
-rw-r--r-- | content/renderer/pepper_plugin_delegate_impl.cc | 106 | ||||
-rw-r--r-- | content/renderer/pepper_plugin_delegate_impl.h | 29 | ||||
-rw-r--r-- | content/renderer/render_view_impl.cc | 69 | ||||
-rw-r--r-- | content/renderer/render_view_impl.h | 7 | ||||
-rw-r--r-- | content/renderer/render_widget.cc | 12 | ||||
-rw-r--r-- | content/renderer/render_widget.h | 1 | ||||
-rw-r--r-- | ppapi/examples/ime/ime.cc | 609 | ||||
-rw-r--r-- | ppapi/examples/ime/ime.html | 31 | ||||
-rw-r--r-- | ppapi/ppapi_tests.gypi | 10 | ||||
-rw-r--r-- | webkit/glue/webkit_glue.gypi | 1 | ||||
-rw-r--r-- | webkit/plugins/ppapi/DEPS | 1 | ||||
-rw-r--r-- | webkit/plugins/ppapi/mock_plugin_delegate.cc | 10 | ||||
-rw-r--r-- | webkit/plugins/ppapi/mock_plugin_delegate.h | 4 | ||||
-rw-r--r-- | webkit/plugins/ppapi/plugin_delegate.h | 9 | ||||
-rw-r--r-- | webkit/plugins/ppapi/ppapi_plugin_instance.cc | 164 | ||||
-rw-r--r-- | webkit/plugins/ppapi/ppapi_plugin_instance.h | 46 | ||||
-rw-r--r-- | webkit/plugins/ppapi/ppb_text_input_impl.cc | 36 |
17 files changed, 1069 insertions, 76 deletions
diff --git a/content/renderer/pepper_plugin_delegate_impl.cc b/content/renderer/pepper_plugin_delegate_impl.cc index 35fc894..5966fdb 100644 --- a/content/renderer/pepper_plugin_delegate_impl.cc +++ b/content/renderer/pepper_plugin_delegate_impl.cc @@ -665,7 +665,7 @@ PepperPluginDelegateImpl::PepperPluginDelegateImpl(RenderViewImpl* render_view) : render_view_(render_view), has_saved_context_menu_action_(false), saved_context_menu_action_(0), - is_pepper_plugin_focused_(false), + focused_plugin_(NULL), mouse_lock_owner_(NULL), mouse_locked_(false), pending_lock_request_(false), @@ -849,12 +849,110 @@ PepperPluginDelegateImpl::GetBitmapForOptimizedPluginPaint( return NULL; } -void PepperPluginDelegateImpl::PluginFocusChanged(bool focused) { - is_pepper_plugin_focused_ = focused; +void PepperPluginDelegateImpl::PluginFocusChanged( + webkit::ppapi::PluginInstance* instance, + bool focused) { + if (focused) + focused_plugin_ = instance; + else if (focused_plugin_ == instance) + focused_plugin_ = NULL; if (render_view_) render_view_->PpapiPluginFocusChanged(); } +void PepperPluginDelegateImpl::PluginTextInputTypeChanged( + webkit::ppapi::PluginInstance* instance) { + if (focused_plugin_ == instance && render_view_) + render_view_->PpapiPluginTextInputTypeChanged(); +} + +void PepperPluginDelegateImpl::PluginRequestedCancelComposition( + webkit::ppapi::PluginInstance* instance) { + if (focused_plugin_ == instance && render_view_) + render_view_->PpapiPluginCancelComposition(); +} + +void PepperPluginDelegateImpl::OnImeSetComposition( + const string16& text, + const std::vector<WebKit::WebCompositionUnderline>& underlines, + int selection_start, + int selection_end) { + if (!IsPluginAcceptingCompositionEvents()) { + composition_text_ = text; + } else { + // TODO(kinaba) currently all composition events are sent directly to + // plugins. Use DOM event mechanism after WebKit is made aware about + // plugins that support composition. + // The code below mimics the behavior of WebCore::Editor::setComposition. + + // Empty -> nonempty: composition started. + if (composition_text_.empty() && !text.empty()) + focused_plugin_->HandleCompositionStart(string16()); + // Nonempty -> empty: composition canceled. + if (!composition_text_.empty() && text.empty()) + focused_plugin_->HandleCompositionEnd(string16()); + composition_text_ = text; + // Nonempty: composition is ongoing. + if (!composition_text_.empty()) { + focused_plugin_->HandleCompositionUpdate(composition_text_, underlines, + selection_start, selection_end); + } + } +} + +void PepperPluginDelegateImpl::OnImeConfirmComposition(const string16& text) { + // Here, text.empty() has a special meaning. It means to commit the last + // update of composition text (see RenderWidgetHost::ImeConfirmComposition()). + const string16& last_text = text.empty() ? composition_text_ : text; + + // last_text is empty only when both text and composition_text_ is. Ignore it. + if (last_text.empty()) + return; + + if (!IsPluginAcceptingCompositionEvents()) { + for (size_t i = 0; i < text.size(); ++i) { + WebKit::WebKeyboardEvent char_event; + char_event.type = WebKit::WebInputEvent::Char; + char_event.timeStampSeconds = base::Time::Now().ToDoubleT(); + char_event.modifiers = 0; + char_event.windowsKeyCode = last_text[i]; + char_event.nativeKeyCode = last_text[i]; + char_event.text[0] = last_text[i]; + char_event.unmodifiedText[0] = last_text[i]; + if (render_view_->webwidget()) + render_view_->webwidget()->handleInputEvent(char_event); + } + } else { + // Mimics the order of events sent by WebKit. + // See WebCore::Editor::setComposition() for the corresponding code. + focused_plugin_->HandleCompositionEnd(last_text); + focused_plugin_->HandleTextInput(last_text); + } + composition_text_.clear(); +} + +gfx::Rect PepperPluginDelegateImpl::GetCaretBounds() const { + if (!focused_plugin_) + return gfx::Rect(0, 0, 0, 0); + return focused_plugin_->GetCaretBounds(); +} + +ui::TextInputType PepperPluginDelegateImpl::GetTextInputType() const { + if (!focused_plugin_) + return ui::TEXT_INPUT_TYPE_NONE; + return focused_plugin_->text_input_type(); +} + +bool PepperPluginDelegateImpl::IsPluginAcceptingCompositionEvents() const { + if (!focused_plugin_) + return false; + return focused_plugin_->IsPluginAcceptingCompositionEvents(); +} + +bool PepperPluginDelegateImpl::CanComposeInline() const { + return IsPluginAcceptingCompositionEvents(); +} + void PepperPluginDelegateImpl::PluginCrashed( webkit::ppapi::PluginInstance* instance) { subscribed_to_policy_updates_.erase(instance); @@ -1053,7 +1151,7 @@ void PepperPluginDelegateImpl::OnSetFocus(bool has_focus) { } bool PepperPluginDelegateImpl::IsPluginFocused() const { - return is_pepper_plugin_focused_; + return focused_plugin_ != NULL; } void PepperPluginDelegateImpl::OnLockMouseACK(bool succeeded) { diff --git a/content/renderer/pepper_plugin_delegate_impl.h b/content/renderer/pepper_plugin_delegate_impl.h index af4d0de..4bcefe3 100644 --- a/content/renderer/pepper_plugin_delegate_impl.h +++ b/content/renderer/pepper_plugin_delegate_impl.h @@ -17,6 +17,7 @@ #include "content/common/content_export.h" #include "ppapi/proxy/broker_dispatcher.h" #include "ppapi/proxy/proxy_channel.h" +#include "ui/base/ime/text_input_type.h" #include "webkit/plugins/ppapi/plugin_delegate.h" #include "webkit/plugins/ppapi/ppb_broker_impl.h" #include "webkit/plugins/ppapi/ppb_flash_menu_impl.h" @@ -46,6 +47,7 @@ class PluginModule; namespace WebKit { class WebFileChooserCompletion; class WebMouseEvent; +struct WebCompositionUnderline; struct WebFileChooserParams; } @@ -166,8 +168,20 @@ class PepperPluginDelegateImpl // notifies all of the plugins. void OnSetFocus(bool has_focus); - // Returns whether or not a Pepper plugin is focused. + // IME status. bool IsPluginFocused() const; + gfx::Rect GetCaretBounds() const; + ui::TextInputType GetTextInputType() const; + bool IsPluginAcceptingCompositionEvents() const; + bool CanComposeInline() const; + + // IME events. + void OnImeSetComposition( + const string16& text, + const std::vector<WebKit::WebCompositionUnderline>& underlines, + int selection_start, + int selection_end); + void OnImeConfirmComposition(const string16& text); // Notification that the request to lock the mouse has completed. void OnLockMouseACK(bool succeeded); @@ -180,7 +194,12 @@ class PepperPluginDelegateImpl bool HandleMouseEvent(const WebKit::WebMouseEvent& event); // PluginDelegate implementation. - virtual void PluginFocusChanged(bool focused) OVERRIDE; + virtual void PluginFocusChanged(webkit::ppapi::PluginInstance* instance, + bool focused) OVERRIDE; + virtual void PluginTextInputTypeChanged( + webkit::ppapi::PluginInstance* instance) OVERRIDE; + virtual void PluginRequestedCancelComposition( + webkit::ppapi::PluginInstance* instance) OVERRIDE; virtual void PluginCrashed(webkit::ppapi::PluginInstance* instance); virtual void InstanceCreated( webkit::ppapi::PluginInstance* instance); @@ -359,7 +378,11 @@ class PepperPluginDelegateImpl BrokerMap pending_connect_broker_; // Whether or not the focus is on a PPAPI plugin - bool is_pepper_plugin_focused_; + webkit::ppapi::PluginInstance* focused_plugin_; + + // Current text input composition text. Empty if no composition is in + // progress. + string16 composition_text_; // Set of instances to receive a notification when the enterprise policy has // been updated. diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc index 9777dbd..fc2d729b 100644 --- a/content/renderer/render_view_impl.cc +++ b/content/renderer/render_view_impl.cc @@ -4156,6 +4156,16 @@ void RenderViewImpl::PpapiPluginFocusChanged() { UpdateInputMethod(); } +void RenderViewImpl::PpapiPluginTextInputTypeChanged() { + UpdateInputMethod(); +} + +void RenderViewImpl::PpapiPluginCancelComposition() { + Send(new ViewHostMsg_ImeCancelComposition(routing_id())); + ui::Range range(ui::Range::InvalidRange()); + Send(new ViewHostMsg_ImeCompositionRangeChanged(routing_id(), range)); +} + void RenderViewImpl::RequestRemoteAccessClientFirewallTraversal() { Send(new ViewHostMsg_RequestRemoteAccessClientFirewallTraversal(routing_id_)); } @@ -4165,13 +4175,13 @@ void RenderViewImpl::OnImeSetComposition( const std::vector<WebKit::WebCompositionUnderline>& underlines, int selection_start, int selection_end) { - // Until PPAPI has an interface for handling IME events, we skip sending - // OnImeSetComposition. Otherwise the composition is canceled when a - // non-editable DOM element is focused. - // - // TODO(kinaba) This temporal remedy can be removed after PPAPI is extended - // with an IME handling interface. - if (!pepper_delegate_.IsPluginFocused()) { + if (pepper_delegate_.IsPluginFocused()) { + // When a PPAPI plugin has focus, we bypass WebKit. + pepper_delegate_.OnImeSetComposition(text, + underlines, + selection_start, + selection_end); + } else { #if defined(OS_WIN) // When a plug-in has focus, we create platform-specific IME data used by // our IME emulator and send it directly to the focused plug-in, i.e. we @@ -4208,20 +4218,8 @@ void RenderViewImpl::OnImeSetComposition( void RenderViewImpl::OnImeConfirmComposition( const string16& text, const ui::Range& replacement_range) { if (pepper_delegate_.IsPluginFocused()) { - // TODO(kinaba) Until PPAPI has an interface for handling IME events, we - // send character events. - for (size_t i = 0; i < text.size(); ++i) { - WebKit::WebKeyboardEvent char_event; - char_event.type = WebKit::WebInputEvent::Char; - char_event.timeStampSeconds = base::Time::Now().ToDoubleT(); - char_event.modifiers = 0; - char_event.windowsKeyCode = text[i]; - char_event.nativeKeyCode = text[i]; - char_event.text[0] = text[i]; - char_event.unmodifiedText[0] = text[i]; - if (webwidget_) - webwidget_->handleInputEvent(char_event); - } + // When a PPAPI plugin has focus, we bypass WebKit. + pepper_delegate_.OnImeConfirmComposition(text); } else { #if defined(OS_WIN) // Same as OnImeSetComposition(), we send the text from IMEs directly to @@ -4242,29 +4240,18 @@ void RenderViewImpl::OnImeConfirmComposition( } ui::TextInputType RenderViewImpl::GetTextInputType() { - if (pepper_delegate_.IsPluginFocused()) { -#if !defined(TOUCH_UI) - // TODO(kinaba) Until PPAPI has an interface for handling IME events, we - // consider all the parts of PPAPI plugins are accepting text inputs. - return ui::TEXT_INPUT_TYPE_TEXT; -#else - // Disable keyboard input in flash for touch ui until PPAPI can support IME - // events. - return ui::TEXT_INPUT_TYPE_NONE; -#endif - } - return RenderWidget::GetTextInputType(); + return pepper_delegate_.IsPluginFocused() ? + pepper_delegate_.GetTextInputType() : RenderWidget::GetTextInputType(); +} + +gfx::Rect RenderViewImpl::GetCaretBounds() { + return pepper_delegate_.IsPluginFocused() ? + pepper_delegate_.GetCaretBounds() : RenderWidget::GetCaretBounds(); } bool RenderViewImpl::CanComposeInline() { - if (pepper_delegate_.IsPluginFocused()) { - // TODO(kinaba) Until PPAPI has an interface for handling IME events, there - // is no way for the browser to know whether the plugin is capable of - // drawing composition text. We assume plugins are incapable and let the - // browser handle composition display for now. - return false; - } - return true; + return pepper_delegate_.IsPluginFocused() ? + pepper_delegate_.CanComposeInline() : true; } #if defined(OS_WIN) diff --git a/content/renderer/render_view_impl.h b/content/renderer/render_view_impl.h index 873c739..2869dce 100644 --- a/content/renderer/render_view_impl.h +++ b/content/renderer/render_view_impl.h @@ -249,6 +249,12 @@ class RenderViewImpl : public RenderWidget, // Informs the render view that a PPAPI plugin has gained or lost focus. void PpapiPluginFocusChanged(); + // Informs the render view that a PPAPI plugin has changed text input status. + void PpapiPluginTextInputTypeChanged(); + + // Cancels current composition. + void PpapiPluginCancelComposition(); + // Request updated policy regarding firewall NAT traversal being enabled. void RequestRemoteAccessClientFirewallTraversal(); @@ -635,6 +641,7 @@ class RenderViewImpl : public RenderWidget, virtual void OnImeConfirmComposition( const string16& text, const ui::Range& replacement_range) OVERRIDE; virtual ui::TextInputType GetTextInputType() OVERRIDE; + virtual gfx::Rect GetCaretBounds() OVERRIDE; virtual bool CanComposeInline() OVERRIDE; virtual bool WebWidgetHandlesCompositorScheduling() const OVERRIDE; diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc index 1e608af..f228f79 100644 --- a/content/renderer/render_widget.cc +++ b/content/renderer/render_widget.cc @@ -1302,10 +1302,7 @@ void RenderWidget::UpdateInputMethod() { ui::TextInputType new_type = GetTextInputType(); bool new_can_compose_inline = CanComposeInline(); - WebRect new_caret_bounds; - - if (webwidget_) - new_caret_bounds = webwidget_->caretOrSelectionBounds(); + WebRect new_caret_bounds = GetCaretBounds(); // Only sends text input type and caret bounds to the browser process if they // are changed. @@ -1319,6 +1316,13 @@ void RenderWidget::UpdateInputMethod() { } } +gfx::Rect RenderWidget::GetCaretBounds() { + if (!webwidget_) + return gfx::Rect(); + return webwidget_->caretOrSelectionBounds(); +} + +// Check WebKit::WebTextInputType and ui::TextInputType is kept in sync. COMPILE_ASSERT(int(WebKit::WebTextInputTypeNone) == \ int(ui::TEXT_INPUT_TYPE_NONE), mismatching_enums); COMPILE_ASSERT(int(WebKit::WebTextInputTypeText) == \ diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h index 397e48c..45bb14c 100644 --- a/content/renderer/render_widget.h +++ b/content/renderer/render_widget.h @@ -285,6 +285,7 @@ class CONTENT_EXPORT RenderWidget // Override point to obtain that the current input method state and caret // position. virtual ui::TextInputType GetTextInputType(); + virtual gfx::Rect GetCaretBounds(); // Override point to obtain that the current input method state about // composition text. diff --git a/ppapi/examples/ime/ime.cc b/ppapi/examples/ime/ime.cc new file mode 100644 index 0000000..b17c9a6 --- /dev/null +++ b/ppapi/examples/ime/ime.cc @@ -0,0 +1,609 @@ +// Copyright (c) 2011 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 <string> +#include <utility> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "ppapi/c/dev/ppb_console_dev.h" +#include "ppapi/c/dev/ppb_cursor_control_dev.h" +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/cpp/dev/font_dev.h" +#include "ppapi/cpp/dev/ime_input_event_dev.h" +#include "ppapi/cpp/dev/text_input_dev.h" +#include "ppapi/cpp/graphics_2d.h" +#include "ppapi/cpp/image_data.h" +#include "ppapi/cpp/input_event.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/rect.h" +#include "ppapi/cpp/size.h" +#include "ui/base/keycodes/keyboard_codes.h" + +namespace { + +const uint32_t kTextfieldBgColor = 0xffffffff; +const uint32_t kTextfieldTextColor = 0xff000000; +const uint32_t kTextfieldCaretColor = 0xff000000; +const uint32_t kTextfieldPreeditTextColor = 0xffff0000; +const uint32_t kTextfieldUnderlineColorMain = 0xffff0000; +const uint32_t kTextfieldUnderlineColorSub = 0xffddaaaa; + +void FillRect(pp::ImageData* image, + int left, int top, int width, int height, + uint32_t color) { + for (int y = std::max(0, top); + y < std::min(image->size().height() - 1, top + height); + ++y) { + for (int x = std::max(0, left); + x < std::min(image->size().width() - 1, left + width); + ++x) + *image->GetAddr32(pp::Point(x, y)) = color; + } +} + +void FillRect(pp::ImageData* image, const pp::Rect& rect, uint32_t color) { + FillRect(image, rect.x(), rect.y(), rect.width(), rect.height(), color); +} + +size_t GetPrevCharOffsetUtf8(const std::string& str, size_t current_pos) { + size_t i = current_pos; + if (i > 0) { + do + --i; + while (i > 0 && (str[i] & 0xc0) == 0x80); + } + return i; +} + +size_t GetNextCharOffsetUtf8(const std::string& str, size_t current_pos) { + size_t i = current_pos; + if (i < str.size()) { + do + ++i; + while (i < str.size() && (str[i] & 0xc0) == 0x80); + } + return i; +} + +size_t GetNthCharOffsetUtf8(const std::string& str, size_t n) { + size_t i = 0; + for (size_t step = 0; step < n; ++step) + i = GetNextCharOffsetUtf8(str, i); + return i; +} + +} // namespace + +class TextFieldStatusHandler { + public: + virtual ~TextFieldStatusHandler() {} + virtual void FocusIn(const pp::Rect& caret, const pp::Rect& bounding_box) {} + virtual void FocusOut() {} +}; + +class TextFieldStatusNotifyingHanlder : public TextFieldStatusHandler { + public: + explicit TextFieldStatusNotifyingHanlder(pp::Instance* instance) + : instance_(instance), + textinput_control_(instance) {} + + protected: + virtual void FocusIn(const pp::Rect& caret, const pp::Rect& bounding_box) { + textinput_control_.SetTextInputType(PP_TEXTINPUT_TYPE_TEXT); + textinput_control_.UpdateCaretPosition(caret, bounding_box); + } + virtual void FocusOut() { + textinput_control_.CancelCompositionText(); + textinput_control_.SetTextInputType(PP_TEXTINPUT_TYPE_NONE); + } + + private: + pp::Instance* instance_; + pp::TextInput_Dev textinput_control_; +}; + +// Hand-made text field for demonstrating text input API. +class MyTextField { + public: + MyTextField(pp::Instance* instance, TextFieldStatusHandler* handler, + int x, int y, int width, int height) + : instance_(instance), + status_handler_(handler), + area_(x, y, width, height), + font_size_(height - 2), + caret_pos_(std::string::npos) { + pp::FontDescription_Dev desc; + desc.set_family(PP_FONTFAMILY_SANSSERIF); + desc.set_size(font_size_); + font_ = pp::Font_Dev(instance_, desc); + } + + // Paint on the specified ImageData. + void PaintOn(pp::ImageData* image, pp::Rect clip) { + clip = clip.Intersect(area_); + FillRect(image, clip, kTextfieldBgColor); + + if (caret_pos_ != std::string::npos) { + int offset = area_.x(); + // before caret + { + std::string str = utf8_text_.substr(0, caret_pos_); + font_.DrawTextAt( + image, + pp::TextRun_Dev(str.c_str(), false, false), + pp::Point(offset, area_.y() + font_size_), + kTextfieldTextColor, + clip, + false); + offset += font_.MeasureSimpleText(str); + } + // composition + { + const std::string& str = composition_; + font_.DrawTextAt( + image, + pp::TextRun_Dev(str.c_str(), false, false), + pp::Point(offset, area_.y() + font_size_), + kTextfieldPreeditTextColor, + clip, + false); + for (size_t i = 0; i < segments_.size(); ++i) { + size_t l = segments_[i].first; + size_t r = segments_[i].second; + if (l != r) { + int lx = font_.MeasureSimpleText(str.substr(0, l)); + int rx = font_.MeasureSimpleText(str.substr(0, r)); + FillRect(image, + offset + lx + 2, area_.y() + font_size_ + 1, + rx - lx - 4, 2, + i == static_cast<size_t>(target_segment_) ? + kTextfieldUnderlineColorMain : + kTextfieldUnderlineColorSub); + } + } + // caret + int caretx = font_.MeasureSimpleText(str.substr(0, selection_.first)); + FillRect(image, + pp::Rect(offset + caretx, area_.y(), 2, area_.height()), + kTextfieldCaretColor); + offset += font_.MeasureSimpleText(str); + } + // after caret + { + std::string str = utf8_text_.substr(caret_pos_); + font_.DrawTextAt( + image, + pp::TextRun_Dev(str.c_str(), false, false), + pp::Point(offset, area_.y() + font_size_), + kTextfieldTextColor, + clip, + false); + } + } else { + font_.DrawTextAt( + image, + pp::TextRun_Dev(utf8_text_.c_str(), false, false), + pp::Point(area_.x(), area_.y() + font_size_), + kTextfieldTextColor, + clip, + false); + } + } + + // Update current composition text. + void SetComposition( + const std::string& text, + const std::vector< std::pair<uint32_t, uint32_t> >& segments, + int32_t target_segment, + const std::pair<uint32_t, uint32_t>& selection) { + composition_ = text; + segments_ = segments; + target_segment_ = target_segment; + selection_ = selection; + CaretPosChanged(); + } + + // Is the text field focused? + bool Focused() const { + return caret_pos_ != std::string::npos; + } + + // Does the coordinate (x,y) is contained inside the edit box? + bool Contains(int x, int y) const { + return area_.Contains(x, y); + } + + // Resets the content text. + void SetText(const std::string& text) { + utf8_text_ = text; + if (Focused()) { + caret_pos_ = text.size(); + CaretPosChanged(); + } + } + + // Inserts a text at the current caret position. + void InsertText(const std::string& text) { + if (!Focused()) + return; + utf8_text_.insert(caret_pos_, text); + if (Focused()) { + caret_pos_ += text.size(); + CaretPosChanged(); + } + } + + // Handles mouse click event and changes the focus state. + bool RefocusByMouseClick(int x, int y) { + if (!Contains(x, y)) { + // The text field is unfocused. + caret_pos_ = std::string::npos; + return false; + } + + // The text field is focused. + size_t n = font_.CharacterOffsetForPixel( + pp::TextRun_Dev(utf8_text_.c_str()), x - area_.x()); + caret_pos_ = GetNthCharOffsetUtf8(utf8_text_, n); + CaretPosChanged(); + return true; + } + + void KeyLeft() { + if (!Focused()) + return; + caret_pos_ = GetPrevCharOffsetUtf8(utf8_text_, caret_pos_); + CaretPosChanged(); + } + + void KeyRight() { + if (!Focused()) + return; + caret_pos_ = GetNextCharOffsetUtf8(utf8_text_, caret_pos_); + CaretPosChanged(); + } + + void KeyDelete() { + if (!Focused()) + return; + size_t i = GetNextCharOffsetUtf8(utf8_text_, caret_pos_); + utf8_text_.erase(caret_pos_, i - caret_pos_); + CaretPosChanged(); + } + + void KeyBackspace() { + if (!Focused() || caret_pos_ == 0) + return; + KeyLeft(); + KeyDelete(); + } + + private: + // Notify the plugin instance that the caret position has changed. + void CaretPosChanged() { + if (Focused()) { + std::string str = utf8_text_.substr(0, caret_pos_); + if (!composition_.empty()) + str += composition_.substr(0, selection_.first); + int px = font_.MeasureSimpleText(str); + pp::Rect caret(area_.x() + px, area_.y(), 0, area_.height() + 2); + status_handler_->FocusIn(caret, area_); + } + } + + pp::Instance* instance_; + TextFieldStatusHandler* status_handler_; + + pp::Rect area_; + int font_size_; + pp::Font_Dev font_; + std::string utf8_text_; + size_t caret_pos_; + std::string composition_; + std::vector< std::pair<uint32_t, uint32_t> > segments_; + std::pair<uint32_t, uint32_t> selection_; + int target_segment_; +}; + +class MyInstance : public pp::Instance { + public: + explicit MyInstance(PP_Instance instance) + : pp::Instance(instance), + status_handler_(new TextFieldStatusHandler) { + } + + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) { + RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE); + RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD); + + for (uint32_t i = 0; i < argc; ++i) { + if (argn[i] == std::string("ime")) { + if (argv[i] == std::string("no")) { + // Example of NO-IME plugins (e.g., games). + // + // When a plugin never wants to accept text input, at initialization + // explicitly turn off the text input feature by calling: + pp::TextInput_Dev(this).SetTextInputType(PP_TEXTINPUT_TYPE_NONE); + } else if (argv[i] == std::string("unaware")) { + // Demonstrating the behavior of IME-unaware plugins. + // Never call any text input related APIs. + // + // In such a case, the plugin is assumed to always accept text input. + // For example, when the plugin is focused in touch devices a virtual + // keyboard may pop up, or in environment IME is used, users can type + // text via IME on the plugin. The characters are delivered to the + // plugin via PP_INPUTEVENT_TYPE_CHAR events. + } else if (argv[i] == std::string("caretmove")) { + // Demonstrating the behavior of plugins with limited IME support. + // + // It uses SetTextInputType() and UpdateCaretPosition() API to notify + // text input status to the browser, but unable to handle inline + // compositions. By using the notified information. the browser can, + // say, show virtual keyboards or IMEs only at appropriate timing + // that the plugin does need to accept text input. + status_handler_.reset(new TextFieldStatusNotifyingHanlder(this)); + } else if (argv[i] == std::string("full")) { + // Demonstrating the behavior of plugins fully supporting IME. + // + // It notifies updates of caret positions to the browser, + // and handles all text input events by itself. + status_handler_.reset(new TextFieldStatusNotifyingHanlder(this)); + RequestInputEvents(PP_INPUTEVENT_CLASS_IME); + } + break; + } + } + + textfield_.push_back(MyTextField(this, status_handler_.get(), + 10, 10, 300, 20)); + textfield_.back().SetText("Hello"); + textfield_.push_back(MyTextField(this, status_handler_.get(), + 30, 100, 300, 20)); + textfield_.back().SetText("World"); + return true; + } + + protected: + virtual bool HandleInputEvent(const pp::InputEvent& event) { + bool ret = false; + switch (event.GetType()) { + case PP_INPUTEVENT_TYPE_MOUSEDOWN: { + const pp::MouseInputEvent mouseEvent(event); + ret = OnMouseDown(mouseEvent); + break; + } + case PP_INPUTEVENT_TYPE_MOUSEMOVE: { + const pp::MouseInputEvent mouseEvent(event); + ret = OnMouseMove(mouseEvent); + break; + } + case PP_INPUTEVENT_TYPE_KEYDOWN: { + Log("Keydown"); + const pp::KeyboardInputEvent keyEvent(event); + ret = OnKeyDown(keyEvent); + break; + } + case PP_INPUTEVENT_TYPE_CHAR: { + const pp::KeyboardInputEvent keyEvent(event); + Log("Char [" + keyEvent.GetCharacterText().AsString() + "]"); + ret = OnChar(keyEvent); + break; + } + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START: { + const pp::IMEInputEvent_Dev imeEvent(event); + Log("CompositionStart [" + imeEvent.GetText().AsString() + "]"); + ret = true; + break; + } + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE: { + const pp::IMEInputEvent_Dev imeEvent(event); + Log("CompositionUpdate [" + imeEvent.GetText().AsString() + "]"); + ret = OnCompositionUpdate(imeEvent); + break; + } + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END: { + const pp::IMEInputEvent_Dev imeEvent(event); + Log("CompositionEnd [" + imeEvent.GetText().AsString() + "]"); + ret = OnCompositionEnd(imeEvent); + break; + } + case PP_INPUTEVENT_TYPE_IME_TEXT: { + const pp::IMEInputEvent_Dev imeEvent(event); + Log("ImeText [" + imeEvent.GetText().AsString() + "]"); + ret = OnImeText(imeEvent); + break; + } + default: + break; + } + if (ret && event.GetType() != PP_INPUTEVENT_TYPE_MOUSEMOVE) + Paint(); + return ret; + } + + virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip) { + if (position.size() == last_size_) + return; + last_size_ = position.size(); + Paint(); + } + + private: + bool OnCompositionUpdate(const pp::IMEInputEvent_Dev& ev) { + for (std::vector<MyTextField>::iterator it = textfield_.begin(); + it != textfield_.end(); + ++it) { + if (it->Focused()) { + std::vector< std::pair<uint32_t, uint32_t> > segs; + for (uint32_t i = 0; i < ev.GetSegmentNumber(); ++i) + segs.push_back(std::make_pair(ev.GetSegmentOffset(i), + ev.GetSegmentOffset(i + 1))); + it->SetComposition(ev.GetText().AsString(), + segs, + ev.GetTargetSegment(), + ev.GetSelection()); + return true; + } + } + return false; + } + + bool OnCompositionEnd(const pp::IMEInputEvent_Dev& ev) { + for (std::vector<MyTextField>::iterator it = textfield_.begin(); + it != textfield_.end(); + ++it) { + if (it->Focused()) { + it->SetComposition("", std::vector< std::pair<uint32_t, uint32_t> >(), + 0, std::make_pair(0, 0)); + return true; + } + } + return false; + } + + bool OnMouseDown(const pp::MouseInputEvent& ev) { + bool anyone_focused = false; + for (std::vector<MyTextField>::iterator it = textfield_.begin(); + it != textfield_.end(); + ++it) { + if (it->RefocusByMouseClick(ev.GetPosition().x(), + ev.GetPosition().y())) { + anyone_focused = true; + } + } + if (!anyone_focused) + status_handler_->FocusOut(); + return true; + } + + bool OnMouseMove(const pp::MouseInputEvent& ev) { + const PPB_CursorControl_Dev* cursor_control = + reinterpret_cast<const PPB_CursorControl_Dev*>( + pp::Module::Get()->GetBrowserInterface( + PPB_CURSOR_CONTROL_DEV_INTERFACE)); + if (!cursor_control) + return false; + + for (std::vector<MyTextField>::iterator it = textfield_.begin(); + it != textfield_.end(); + ++it) { + if (it->Contains(ev.GetPosition().x(), + ev.GetPosition().y())) { + cursor_control->SetCursor(pp_instance(), PP_CURSORTYPE_IBEAM, + 0, NULL); + return true; + } + } + cursor_control->SetCursor(pp_instance(), PP_CURSORTYPE_POINTER, + 0, NULL); + return true; + } + + bool OnKeyDown(const pp::KeyboardInputEvent& ev) { + for (std::vector<MyTextField>::iterator it = textfield_.begin(); + it != textfield_.end(); + ++it) { + if (it->Focused()) { + switch (ev.GetKeyCode()) { + case ui::VKEY_LEFT: + it->KeyLeft(); + break; + case ui::VKEY_RIGHT: + it->KeyRight(); + break; + case ui::VKEY_DELETE: + it->KeyDelete(); + break; + case ui::VKEY_BACK: + it->KeyBackspace(); + break; + } + return true; + } + } + return false; + } + + bool OnChar(const pp::KeyboardInputEvent& ev) { + for (std::vector<MyTextField>::iterator it = textfield_.begin(); + it != textfield_.end(); + ++it) { + if (it->Focused()) { + std::string str = ev.GetCharacterText().AsString(); + if (str != "\r" && str != "\n") + it->InsertText(str); + return true; + } + } + return false; + } + + bool OnImeText(const pp::IMEInputEvent_Dev ev) { + for (std::vector<MyTextField>::iterator it = textfield_.begin(); + it != textfield_.end(); + ++it) { + if (it->Focused()) { + it->InsertText(ev.GetText().AsString()); + return true; + } + } + return false; + } + + void Paint() { + pp::Rect clip(0, 0, last_size_.width(), last_size_.height()); + PaintClip(clip); + } + + void PaintClip(const pp::Rect& clip) { + pp::ImageData image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL, last_size_, true); + pp::Graphics2D device(this, last_size_, false); + BindGraphics(device); + + for (std::vector<MyTextField>::iterator it = textfield_.begin(); + it != textfield_.end(); + ++it) { + it->PaintOn(&image, clip); + } + + device.PaintImageData(image, pp::Point(0, 0)); + device.Flush(pp::CompletionCallback(&OnFlush, this)); + } + + static void OnFlush(void* user_data, int32_t result) {} + + // Prints a debug message. + void Log(const pp::Var& value) { + const PPB_Console_Dev* console = reinterpret_cast<const PPB_Console_Dev*>( + pp::Module::Get()->GetBrowserInterface(PPB_CONSOLE_DEV_INTERFACE)); + if (!console) + return; + console->Log(pp_instance(), PP_LOGLEVEL_LOG, value.pp_var()); + } + + // IME Control interface. + scoped_ptr<TextFieldStatusHandler> status_handler_; + + // Remembers the size of this instance. + pp::Size last_size_; + + // Holds instances of text fields. + std::vector<MyTextField> textfield_; +}; + +class MyModule : public pp::Module { + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new MyInstance(instance); + } +}; + +namespace pp { + +Module* CreateModule() { + return new MyModule(); +} + +} // namespace pp diff --git a/ppapi/examples/ime/ime.html b/ppapi/examples/ime/ime.html new file mode 100644 index 0000000..a9f056c --- /dev/null +++ b/ppapi/examples/ime/ime.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html> + <!-- + Copyright (c) 2011 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. + --> +<head> +<title>IME Example</title> +</head> +<body style="background-color:#eef"> + <p>Full IME Support</p> + <object id="plugin1" type="application/x-ppapi-example" width="400" height="150" + border="2px"><param name="ime" value="full" /></object> + + <p>CaretMoveOnly</p> + <object id="plugin2" type="application/x-ppapi-example" width="400" height="150" + border="2px"><param name="ime" value="caretmove" /></object> + + <p>IME-Unaware</p> + <object id="plugin3" type="application/x-ppapi-example" width="400" height="150" + border="2px"><param name="ime" value="unaware" /></object> + + <p>No IME (explicitly turn IME off)</p> + <object id="plugin4" type="application/x-ppapi-example" width="400" height="150" + border="2px"><param name="ime" value="no" /></object> + + <p>HTML Textarea</p> + <textarea width="200" height="3"></textarea> +</body> +</html> diff --git a/ppapi/ppapi_tests.gypi b/ppapi/ppapi_tests.gypi index c5f2aa8..08e661e 100644 --- a/ppapi/ppapi_tests.gypi +++ b/ppapi/ppapi_tests.gypi @@ -313,6 +313,16 @@ ], }, { + 'target_name': 'ppapi_example_ime', + 'dependencies': [ + 'ppapi_example_skeleton', + 'ppapi.gyp:ppapi_cpp', + ], + 'sources': [ + 'examples/ime/ime.cc', + ], + }, + { 'target_name': 'ppapi_example_paint_manager', 'dependencies': [ 'ppapi_example_skeleton', diff --git a/webkit/glue/webkit_glue.gypi b/webkit/glue/webkit_glue.gypi index 83b09fd..89c42c2 100644 --- a/webkit/glue/webkit_glue.gypi +++ b/webkit/glue/webkit_glue.gypi @@ -116,6 +116,7 @@ '<(DEPTH)/third_party/icu/icu.gyp:icui18n', '<(DEPTH)/third_party/icu/icu.gyp:icuuc', '<(DEPTH)/third_party/npapi/npapi.gyp:npapi', + '<(DEPTH)/ui/ui.gyp:ui', '<(DEPTH)/v8/tools/gyp/v8.gyp:v8', 'webkit_resources', 'webkit_strings', diff --git a/webkit/plugins/ppapi/DEPS b/webkit/plugins/ppapi/DEPS index ffafa6f..a2b6af6 100644 --- a/webkit/plugins/ppapi/DEPS +++ b/webkit/plugins/ppapi/DEPS @@ -4,6 +4,7 @@ include_rules = [ "+ppapi/thunk", "+printing", "+media/video", + "+ui/base/ime", # This should technically not be allowed. Brett is refactoring this and will # move this file to a more proper shared location in a future iteration. diff --git a/webkit/plugins/ppapi/mock_plugin_delegate.cc b/webkit/plugins/ppapi/mock_plugin_delegate.cc index 09cc038..8aeacdd 100644 --- a/webkit/plugins/ppapi/mock_plugin_delegate.cc +++ b/webkit/plugins/ppapi/mock_plugin_delegate.cc @@ -18,7 +18,15 @@ MockPluginDelegate::MockPluginDelegate() { MockPluginDelegate::~MockPluginDelegate() { } -void MockPluginDelegate::PluginFocusChanged(bool focused) { +void MockPluginDelegate::PluginFocusChanged(PluginInstance* instance, + bool focused) { +} + +void MockPluginDelegate::PluginTextInputTypeChanged(PluginInstance* instance) { +} + +void MockPluginDelegate::PluginRequestedCancelComposition( + PluginInstance* instance) { } void MockPluginDelegate::PluginCrashed(PluginInstance* instance) { diff --git a/webkit/plugins/ppapi/mock_plugin_delegate.h b/webkit/plugins/ppapi/mock_plugin_delegate.h index 29c989b..bef5340 100644 --- a/webkit/plugins/ppapi/mock_plugin_delegate.h +++ b/webkit/plugins/ppapi/mock_plugin_delegate.h @@ -15,7 +15,9 @@ class MockPluginDelegate : public PluginDelegate { MockPluginDelegate(); virtual ~MockPluginDelegate(); - virtual void PluginFocusChanged(bool focused); + virtual void PluginFocusChanged(PluginInstance* instance, bool focused); + virtual void PluginTextInputTypeChanged(PluginInstance* instance); + virtual void PluginRequestedCancelComposition(PluginInstance* instance); virtual void PluginCrashed(PluginInstance* instance); virtual void InstanceCreated(PluginInstance* instance); virtual void InstanceDeleted(PluginInstance* instance); diff --git a/webkit/plugins/ppapi/plugin_delegate.h b/webkit/plugins/ppapi/plugin_delegate.h index 0cbeabf..3200431 100644 --- a/webkit/plugins/ppapi/plugin_delegate.h +++ b/webkit/plugins/ppapi/plugin_delegate.h @@ -246,7 +246,14 @@ class PluginDelegate { }; // Notification that the given plugin is focused or unfocused. - virtual void PluginFocusChanged(bool focused) = 0; + virtual void PluginFocusChanged(webkit::ppapi::PluginInstance* instance, + bool focused) = 0; + // Notification that the text input status of the given plugin is changed. + virtual void PluginTextInputTypeChanged( + webkit::ppapi::PluginInstance* instance) = 0; + // Notification that the plugin requested to cancel the current composition. + virtual void PluginRequestedCancelComposition( + webkit::ppapi::PluginInstance* instance) = 0; // Notification that the given plugin has crashed. When a plugin crashes, all // instances associated with that plugin will notify that they've crashed via diff --git a/webkit/plugins/ppapi/ppapi_plugin_instance.cc b/webkit/plugins/ppapi/ppapi_plugin_instance.cc index 9824c67..34717e1 100644 --- a/webkit/plugins/ppapi/ppapi_plugin_instance.cc +++ b/webkit/plugins/ppapi/ppapi_plugin_instance.cc @@ -10,6 +10,7 @@ #include "base/memory/scoped_ptr.h" #include "base/message_loop.h" #include "base/metrics/histogram.h" +#include "base/utf_offset_string_conversions.h" #include "base/utf_string_conversions.h" #include "ppapi/c/dev/ppb_console_dev.h" #include "ppapi/c/dev/ppb_find_dev.h" @@ -34,6 +35,7 @@ #include "ppapi/c/private/ppp_instance_private.h" #include "ppapi/shared_impl/input_event_impl.h" #include "ppapi/shared_impl/resource.h" +#include "ppapi/shared_impl/time_conversion.h" #include "ppapi/shared_impl/url_util_impl.h" #include "ppapi/shared_impl/var.h" #include "ppapi/thunk/enter.h" @@ -134,6 +136,20 @@ typedef bool (*RenderPDFPageToDCProc)( namespace { +#if !defined(TOUCH_UI) +// The default text input type is to regard the plugin always accept text input. +// This is for allowing users to use input methods even on completely-IME- +// unaware plugins (e.g., PPAPI Flash or PDF plugin for M16). +// Plugins need to explicitly opt out the text input mode if they know +// that they don't accept texts. +const ui::TextInputType kPluginDefaultTextInputType = ui::TEXT_INPUT_TYPE_TEXT; +#else +// On the other hand, for touch ui, accepting text input implies to pop up +// virtual keyboard always. It makes IME-unaware plugins almost unusable, +// and hence is disabled by default (codereview.chromium.org/7800044). +const ui::TextInputType kPluginDefaultTextInputType = ui::TEXT_INPUT_TYPE_NONE; +#endif + #define COMPILE_ASSERT_MATCHING_ENUM(webkit_name, np_name) \ COMPILE_ASSERT(static_cast<int>(WebCursorInfo::webkit_name) \ == static_cast<int>(np_name), \ @@ -266,6 +282,10 @@ PluginInstance::PluginInstance( sad_plugin_(NULL), input_event_mask_(0), filtered_input_event_mask_(0), + text_input_type_(kPluginDefaultTextInputType), + text_input_caret_(0, 0, 0, 0), + text_input_caret_bounds_(0, 0, 0, 0), + text_input_caret_set_(false), lock_mouse_callback_(PP_BlockUntilComplete()) { pp_instance_ = ResourceTracker::Get()->AddInstance(this); @@ -475,6 +495,148 @@ bool PluginInstance::HandleDocumentLoad(PPB_URLLoader_Impl* loader) { pp_instance(), loader->pp_resource())); } +bool PluginInstance::SendCompositionEventToPlugin(PP_InputEvent_Type type, + const string16& text) { + std::vector<WebKit::WebCompositionUnderline> empty; + return SendCompositionEventWithUnderlineInformationToPlugin( + type, text, empty, static_cast<int>(text.size()), + static_cast<int>(text.size())); +} + +bool PluginInstance::SendCompositionEventWithUnderlineInformationToPlugin( + PP_InputEvent_Type type, + const string16& text, + const std::vector<WebKit::WebCompositionUnderline>& underlines, + int selection_start, + int selection_end) { + // Keep a reference on the stack. See NOTE above. + scoped_refptr<PluginInstance> ref(this); + + if (!LoadInputEventInterface()) + return false; + + PP_InputEvent_Class event_class = PP_INPUTEVENT_CLASS_IME; + if (!(filtered_input_event_mask_ & event_class) && + !(input_event_mask_ & event_class)) + return false; + + ::ppapi::InputEventData event; + event.event_type = type; + event.event_time_stamp = ::ppapi::TimeTicksToPPTimeTicks( + base::TimeTicks::Now()); + + // Convert UTF16 text to UTF8 with offset conversion. + std::vector<size_t> utf16_offsets; + utf16_offsets.push_back(selection_start); + utf16_offsets.push_back(selection_end); + for (size_t i = 0; i < underlines.size(); ++i) { + utf16_offsets.push_back(underlines[i].startOffset); + utf16_offsets.push_back(underlines[i].endOffset); + } + std::vector<size_t> utf8_offsets(utf16_offsets); + event.character_text = UTF16ToUTF8AndAdjustOffsets(text, &utf8_offsets); + + // Set the converted selection range. + event.composition_selection_start = (utf8_offsets[0] == std::string::npos ? + event.character_text.size() : utf8_offsets[0]); + event.composition_selection_end = (utf8_offsets[1] == std::string::npos ? + event.character_text.size() : utf8_offsets[1]); + + // Set the converted segmentation points. + // Be sure to add 0 and size(), and remove duplication or errors. + std::set<size_t> offset_set(utf8_offsets.begin()+2, utf8_offsets.end()); + offset_set.insert(0); + offset_set.insert(event.character_text.size()); + offset_set.erase(std::string::npos); + event.composition_segment_offsets.assign(offset_set.begin(), + offset_set.end()); + + // Set the composition target. + for (size_t i = 0; i < underlines.size(); ++i) { + if (underlines[i].thick) { + std::vector<uint32_t>::iterator it = + std::find(event.composition_segment_offsets.begin(), + event.composition_segment_offsets.end(), + utf8_offsets[2*i+2]); + if (it != event.composition_segment_offsets.end()) { + event.composition_target_segment = + it - event.composition_segment_offsets.begin(); + break; + } + } + } + + // Send the event. + bool handled = false; + if (filtered_input_event_mask_ & event_class) + event.is_filtered = true; + else + handled = true; // Unfiltered events are assumed to be handled. + scoped_refptr<InputEventImpl> event_resource( + new InputEventImpl(InputEventImpl::InitAsImpl(), + pp_instance(), event)); + handled |= PP_ToBool(plugin_input_event_interface_->HandleInputEvent( + pp_instance(), event_resource->pp_resource())); + return handled; +} + +bool PluginInstance::HandleCompositionStart(const string16& text) { + return SendCompositionEventToPlugin(PP_INPUTEVENT_TYPE_IME_COMPOSITION_START, + text); +} + +bool PluginInstance::HandleCompositionUpdate( + const string16& text, + const std::vector<WebKit::WebCompositionUnderline>& underlines, + int selection_start, + int selection_end) { + return SendCompositionEventWithUnderlineInformationToPlugin( + PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE, + text, underlines, selection_start, selection_end); +} + +bool PluginInstance::HandleCompositionEnd(const string16& text) { + return SendCompositionEventToPlugin(PP_INPUTEVENT_TYPE_IME_COMPOSITION_END, + text); +} + +bool PluginInstance::HandleTextInput(const string16& text) { + return SendCompositionEventToPlugin(PP_INPUTEVENT_TYPE_IME_TEXT, + text); +} + +void PluginInstance::UpdateCaretPosition(const gfx::Rect& caret, + const gfx::Rect& bounding_box) { + text_input_caret_ = caret; + text_input_caret_bounds_ = bounding_box; + text_input_caret_set_ = true; +} + +void PluginInstance::SetTextInputType(ui::TextInputType type) { + text_input_type_ = type; +} + +bool PluginInstance::IsPluginAcceptingCompositionEvents() const { + return (filtered_input_event_mask_ & PP_INPUTEVENT_CLASS_IME) || + (input_event_mask_ & PP_INPUTEVENT_CLASS_IME); +} + +gfx::Rect PluginInstance::GetCaretBounds() const { + if (!text_input_caret_set_) { + // If it is never set by the plugin, use the bottom left corner. + return gfx::Rect(position().x(), position().y()+position().height(), 0, 0); + } + + // TODO(kinaba) Take CSS transformation into accont. + // TODO(kinaba) Take bounding_box into account. On some platforms, an + // "exclude rectangle" where candidate window must avoid the region can be + // passed to IME. Currently, we pass only the caret rectangle because + // it is the only information supported uniformly in Chromium. + gfx::Rect caret(text_input_caret_); + caret.Offset(position().origin()); + return caret; +} + bool PluginInstance::HandleInputEvent(const WebKit::WebInputEvent& event, WebCursorInfo* cursor_info) { TRACE_EVENT0("ppapi", "PluginInstance::HandleInputEvent"); @@ -583,7 +745,7 @@ void PluginInstance::SetWebKitFocus(bool has_focus) { bool old_plugin_focus = PluginHasFocus(); has_webkit_focus_ = has_focus; if (PluginHasFocus() != old_plugin_focus) { - delegate()->PluginFocusChanged(PluginHasFocus()); + delegate()->PluginFocusChanged(this, PluginHasFocus()); instance_interface_->DidChangeFocus(pp_instance(), PP_FromBool(PluginHasFocus())); } diff --git a/webkit/plugins/ppapi/ppapi_plugin_instance.h b/webkit/plugins/ppapi/ppapi_plugin_instance.h index a7356b6..00fbfab 100644 --- a/webkit/plugins/ppapi/ppapi_plugin_instance.h +++ b/webkit/plugins/ppapi/ppapi_plugin_instance.h @@ -22,6 +22,7 @@ #include "ppapi/c/pp_instance.h" #include "ppapi/c/pp_resource.h" #include "ppapi/c/pp_var.h" +#include "ppapi/c/ppb_input_event.h" #include "ppapi/c/ppp_graphics_3d.h" #include "ppapi/c/ppp_instance.h" #include "ppapi/shared_impl/function_group_base.h" @@ -31,6 +32,7 @@ #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkRefCnt.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebCanvas.h" +#include "ui/base/ime/text_input_type.h" #include "ui/gfx/rect.h" #include "webkit/plugins/ppapi/plugin_delegate.h" @@ -48,14 +50,11 @@ struct PPP_Zoom_Dev; class SkBitmap; class TransportDIB; -namespace gfx { -class Rect; -} - namespace WebKit { -struct WebCursorInfo; class WebInputEvent; class WebPluginContainer; +struct WebCompositionUnderline; +struct WebCursorInfo; } namespace ppapi { @@ -167,6 +166,26 @@ class PluginInstance : public base::RefCounted<PluginInstance>, PP_Var GetInstanceObject(); void ViewChanged(const gfx::Rect& position, const gfx::Rect& clip); + // Handlers for composition events. + bool HandleCompositionStart(const string16& text); + bool HandleCompositionUpdate( + const string16& text, + const std::vector<WebKit::WebCompositionUnderline>& underlines, + int selection_start, + int selection_end); + bool HandleCompositionEnd(const string16& text); + bool HandleTextInput(const string16& text); + + // Implementation of composition API. + void UpdateCaretPosition(const gfx::Rect& caret, + const gfx::Rect& bounding_box); + void SetTextInputType(ui::TextInputType type); + + // Gets the current text input status. + ui::TextInputType text_input_type() const { return text_input_type_; } + gfx::Rect GetCaretBounds() const; + bool IsPluginAcceptingCompositionEvents() const; + // Notifications about focus changes, see has_webkit_focus_ below. void SetWebKitFocus(bool has_focus); void SetContentAreaFocus(bool has_focus); @@ -412,6 +431,17 @@ class PluginInstance : public base::RefCounted<PluginInstance>, void DoSetCursor(WebKit::WebCursorInfo* cursor); + // Internal helper functions for HandleCompositionXXX(). + bool SendCompositionEventToPlugin( + PP_InputEvent_Type type, + const string16& text); + bool SendCompositionEventWithUnderlineInformationToPlugin( + PP_InputEvent_Type type, + const string16& text, + const std::vector<WebKit::WebCompositionUnderline>& underlines, + int selection_start, + int selection_end); + // Checks if the security origin of the document containing this instance can // assess the security origin of the main frame document. bool CanAccessMainFrame() const; @@ -555,6 +585,12 @@ class PluginInstance : public base::RefCounted<PluginInstance>, uint32_t input_event_mask_; uint32_t filtered_input_event_mask_; + // Text composition status. + ui::TextInputType text_input_type_; + gfx::Rect text_input_caret_; + gfx::Rect text_input_caret_bounds_; + bool text_input_caret_set_; + PP_CompletionCallback lock_mouse_callback_; DISALLOW_COPY_AND_ASSIGN(PluginInstance); diff --git a/webkit/plugins/ppapi/ppb_text_input_impl.cc b/webkit/plugins/ppapi/ppb_text_input_impl.cc index fc02925..9a1cc92 100644 --- a/webkit/plugins/ppapi/ppb_text_input_impl.cc +++ b/webkit/plugins/ppapi/ppb_text_input_impl.cc @@ -5,7 +5,8 @@ #include "webkit/plugins/ppapi/ppb_text_input_impl.h" #include "base/logging.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebTextInputType.h" +#include "ui/base/ime/text_input_type.h" +#include "ui/gfx/rect.h" #include "webkit/plugins/ppapi/ppapi_plugin_instance.h" namespace webkit { @@ -20,39 +21,44 @@ PPB_TextInput_Impl::AsPPB_TextInput_FunctionAPI() { return this; } -COMPILE_ASSERT(int(WebKit::WebTextInputTypeNone) == \ +// Check PP_TextInput_Type and ui::TextInputType are kept in sync. +COMPILE_ASSERT(int(ui::TEXT_INPUT_TYPE_NONE) == \ int(PP_TEXTINPUT_TYPE_NONE), mismatching_enums); -COMPILE_ASSERT(int(WebKit::WebTextInputTypeText) == \ +COMPILE_ASSERT(int(ui::TEXT_INPUT_TYPE_TEXT) == \ int(PP_TEXTINPUT_TYPE_TEXT), mismatching_enums); -COMPILE_ASSERT(int(WebKit::WebTextInputTypePassword) == \ +COMPILE_ASSERT(int(ui::TEXT_INPUT_TYPE_PASSWORD) == \ int(PP_TEXTINPUT_TYPE_PASSWORD), mismatching_enums); -COMPILE_ASSERT(int(WebKit::WebTextInputTypeSearch) == \ +COMPILE_ASSERT(int(ui::TEXT_INPUT_TYPE_SEARCH) == \ int(PP_TEXTINPUT_TYPE_SEARCH), mismatching_enums); -COMPILE_ASSERT(int(WebKit::WebTextInputTypeEmail) == \ +COMPILE_ASSERT(int(ui::TEXT_INPUT_TYPE_EMAIL) == \ int(PP_TEXTINPUT_TYPE_EMAIL), mismatching_enums); -COMPILE_ASSERT(int(WebKit::WebTextInputTypeNumber) == \ +COMPILE_ASSERT(int(ui::TEXT_INPUT_TYPE_NUMBER) == \ int(PP_TEXTINPUT_TYPE_NUMBER), mismatching_enums); -COMPILE_ASSERT(int(WebKit::WebTextInputTypeTelephone) == \ +COMPILE_ASSERT(int(ui::TEXT_INPUT_TYPE_TELEPHONE) == \ int(PP_TEXTINPUT_TYPE_TELEPHONE), mismatching_enums); -COMPILE_ASSERT(int(WebKit::WebTextInputTypeURL) == \ +COMPILE_ASSERT(int(ui::TEXT_INPUT_TYPE_URL) == \ int(PP_TEXTINPUT_TYPE_URL), mismatching_enums); void PPB_TextInput_Impl::SetTextInputType(PP_Instance instance, PP_TextInput_Type type) { - // TODO(kinaba) the implementation is split to another CL for reviewing. - NOTIMPLEMENTED(); + int itype = type; + if (itype < 0 || itype > ui::TEXT_INPUT_TYPE_URL) + itype = ui::TEXT_INPUT_TYPE_NONE; + instance_->SetTextInputType(static_cast<ui::TextInputType>(itype)); } void PPB_TextInput_Impl::UpdateCaretPosition(PP_Instance instance, const PP_Rect& caret, const PP_Rect& bounding_box) { - // TODO(kinaba) the implementation is split to another CL for reviewing. - NOTIMPLEMENTED(); + instance_->UpdateCaretPosition( + gfx::Rect(caret.point.x, caret.point.y, + caret.size.width, caret.size.height), + gfx::Rect(bounding_box.point.x, bounding_box.point.y, + bounding_box.size.width, bounding_box.size.height)); } void PPB_TextInput_Impl::CancelCompositionText(PP_Instance instance) { - // TODO(kinaba) the implementation is split to another CL for reviewing. - NOTIMPLEMENTED(); + instance_->delegate()->PluginRequestedCancelComposition(instance_); } } // namespace ppapi |