| Index: ui/base/ime/win/tsf_event_router.cc | 
| diff --git a/ui/base/ime/win/tsf_event_router.cc b/ui/base/ime/win/tsf_event_router.cc | 
| index 92e20f31ca5e53d3e0e7cdff3630e3685d1cc190..4cf625ecb16d3ce6d724e86fa430a82d8a949822 100644 | 
| --- a/ui/base/ime/win/tsf_event_router.cc | 
| +++ b/ui/base/ime/win/tsf_event_router.cc | 
| @@ -11,6 +11,7 @@ | 
| #include "base/bind.h" | 
| #include "base/win/scoped_comptr.h" | 
| #include "base/win/metro.h" | 
| +#include "ui/base/range/range.h" | 
| #include "ui/base/win/atl_module.h" | 
|  | 
| namespace ui { | 
| @@ -56,8 +57,9 @@ class ATL_NO_VTABLE TsfEventRouter::TsfEventRouterDelegate | 
| void SetRouter(TsfEventRouter* router); | 
|  | 
| private: | 
| -  // Returns true if the given |context| is composing. | 
| -  static bool IsImeComposingInternal(ITfContext* context); | 
| +  // Returns current composition range. Returns ui::Range::InvalidRange if there | 
| +  // is no composition. | 
| +  static ui::Range GetCompositionRange(ITfContext* context); | 
|  | 
| // Returns true if the given |element_id| represents the candidate window. | 
| bool IsCandidateWindowInternal(DWORD element_id); | 
| @@ -84,6 +86,7 @@ class ATL_NO_VTABLE TsfEventRouter::TsfEventRouterDelegate | 
| DWORD ui_source_cookie_; | 
|  | 
| TsfEventRouter* router_; | 
| +  ui::Range previous_composition_range_; | 
|  | 
| DISALLOW_COPY_AND_ASSIGN(TsfEventRouterDelegate); | 
| }; | 
| @@ -91,7 +94,8 @@ class ATL_NO_VTABLE TsfEventRouter::TsfEventRouterDelegate | 
| TsfEventRouter::TsfEventRouterDelegate::TsfEventRouterDelegate() | 
| : context_source_cookie_(TF_INVALID_COOKIE), | 
| ui_source_cookie_(TF_INVALID_COOKIE), | 
| -      router_(NULL) { | 
| +      router_(NULL), | 
| +      previous_composition_range_(ui::Range::InvalidRange()) { | 
| } | 
|  | 
| TsfEventRouter::TsfEventRouterDelegate::~TsfEventRouterDelegate() {} | 
| @@ -123,10 +127,20 @@ STDMETHODIMP TsfEventRouter::TsfEventRouterDelegate::OnEndEdit( | 
| if (FAILED(ranges->Next(1, range.Receive(), &fetched_count))) | 
| return S_OK;  // Don't care about failures. | 
|  | 
| +  const ui::Range composition_range = GetCompositionRange(context); | 
| + | 
| +  if (!previous_composition_range_.IsValid() && composition_range.IsValid()) | 
| +    router_->OnTsfStartComposition(); | 
| + | 
| // |fetched_count| != 0 means there is at least one range that contains | 
| // updated text. | 
| if (fetched_count != 0) | 
| -    router_->OnTextUpdated(); | 
| +    router_->OnTextUpdated(composition_range); | 
| + | 
| +  if (previous_composition_range_.IsValid() && !composition_range.IsValid()) | 
| +    router_->OnTsfEndComposition(); | 
| + | 
| +  previous_composition_range_ = composition_range; | 
| return S_OK; | 
| } | 
|  | 
| @@ -198,23 +212,39 @@ void TsfEventRouter::TsfEventRouterDelegate::SetManager( | 
| } | 
|  | 
| bool TsfEventRouter::TsfEventRouterDelegate::IsImeComposing() { | 
| -  return context_ && IsImeComposingInternal(context_); | 
| +  return context_ && GetCompositionRange(context_).IsValid(); | 
| } | 
|  | 
| // static | 
| -bool TsfEventRouter::TsfEventRouterDelegate::IsImeComposingInternal( | 
| +ui::Range TsfEventRouter::TsfEventRouterDelegate::GetCompositionRange( | 
| ITfContext* context) { | 
| DCHECK(context); | 
| base::win::ScopedComPtr<ITfContextComposition> context_composition; | 
| if (FAILED(context_composition.QueryFrom(context))) | 
| -    return false; | 
| +    return ui::Range::InvalidRange(); | 
| base::win::ScopedComPtr<IEnumITfCompositionView> enum_composition_view; | 
| if (FAILED(context_composition->EnumCompositions( | 
| enum_composition_view.Receive()))) | 
| -    return false; | 
| +    return ui::Range::InvalidRange(); | 
| base::win::ScopedComPtr<ITfCompositionView> composition_view; | 
| -  return enum_composition_view->Next(1, composition_view.Receive(), | 
| -                                     NULL) == S_OK; | 
| +  if (enum_composition_view->Next(1, composition_view.Receive(), | 
| +                                  NULL) != S_OK) | 
| +    return ui::Range::InvalidRange(); | 
| + | 
| +  base::win::ScopedComPtr<ITfRange> range; | 
| +  if (FAILED(composition_view->GetRange(range.Receive()))) | 
| +    return ui::Range::InvalidRange(); | 
| + | 
| +  base::win::ScopedComPtr<ITfRangeACP> range_acp; | 
| +  if (FAILED(range_acp.QueryFrom(range))) | 
| +    return ui::Range::InvalidRange(); | 
| + | 
| +  LONG start = 0; | 
| +  LONG length = 0; | 
| +  if (FAILED(range_acp->GetExtent(&start, &length))) | 
| +    return ui::Range::InvalidRange(); | 
| + | 
| +  return ui::Range(start, start + length); | 
| } | 
|  | 
| bool TsfEventRouter::TsfEventRouterDelegate::IsCandidateWindowInternal( | 
| @@ -254,20 +284,28 @@ TsfEventRouter::~TsfEventRouter() { | 
| } | 
| } | 
|  | 
| -void TsfEventRouter::SetManager(ITfThreadMgr* thread_manager) { | 
| -  delegate_->SetManager(thread_manager); | 
| -} | 
| - | 
| bool TsfEventRouter::IsImeComposing() { | 
| return delegate_->IsImeComposing(); | 
| } | 
|  | 
| -void TsfEventRouter::OnTextUpdated() { | 
| -  observer_->OnTextUpdated(); | 
| -} | 
| - | 
| void TsfEventRouter::OnCandidateWindowCountChanged(size_t window_count) { | 
| observer_->OnCandidateWindowCountChanged(window_count); | 
| } | 
|  | 
| +void TsfEventRouter::OnTsfStartComposition() { | 
| +  observer_->OnTsfStartComposition(); | 
| +} | 
| + | 
| +void TsfEventRouter::OnTextUpdated(const ui::Range& composition_range) { | 
| +  observer_->OnTextUpdated(composition_range); | 
| +} | 
| + | 
| +void TsfEventRouter::OnTsfEndComposition() { | 
| +  observer_->OnTsfEndComposition(); | 
| +} | 
| + | 
| +void TsfEventRouter::SetManager(ITfThreadMgr* thread_manager) { | 
| +  delegate_->SetManager(thread_manager); | 
| +} | 
| + | 
| }  // namespace ui | 
|  |