Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "ui/base/ime/win/tsf_event_router.h" | 5 #include "ui/base/ime/win/tsf_event_router.h" |
| 6 | 6 |
| 7 #include <msctf.h> | 7 #include <msctf.h> |
| 8 #include <set> | 8 #include <set> |
| 9 #include <utility> | 9 #include <utility> |
| 10 | 10 |
| 11 #include "base/bind.h" | 11 #include "base/bind.h" |
| 12 #include "base/win/scoped_comptr.h" | 12 #include "base/win/scoped_comptr.h" |
| 13 #include "base/win/metro.h" | 13 #include "base/win/metro.h" |
| 14 #include "ui/base/win/atl_module.h" | |
| 14 | 15 |
| 15 namespace ui { | 16 namespace ui { |
| 16 | 17 |
| 17 class TsfEventRouterImpl : public TsfEventRouter, | |
| 18 public ITfUIElementSink, | |
| 19 public ITfTextEditSink { | |
| 20 public: | |
| 21 TsfEventRouterImpl() | |
| 22 : observer_(NULL), | |
| 23 context_source_cookie_(TF_INVALID_COOKIE), | |
| 24 ui_source_cookie_(TF_INVALID_COOKIE), | |
| 25 ref_count_(0) {} | |
| 26 | |
| 27 virtual ~TsfEventRouterImpl() {} | |
| 28 | |
| 29 // ITfTextEditSink override. | |
| 30 virtual ULONG STDMETHODCALLTYPE AddRef() OVERRIDE { | |
| 31 return InterlockedIncrement(&ref_count_); | |
| 32 } | |
| 33 | |
| 34 // ITfTextEditSink override. | |
| 35 virtual ULONG STDMETHODCALLTYPE Release() OVERRIDE { | |
| 36 const LONG count = InterlockedDecrement(&ref_count_); | |
| 37 if (!count) { | |
| 38 delete this; | |
| 39 return 0; | |
| 40 } | |
| 41 return static_cast<ULONG>(count); | |
| 42 } | |
| 43 | |
| 44 // ITfTextEditSink override. | |
| 45 virtual STDMETHODIMP QueryInterface(REFIID iid, void** result) OVERRIDE { | |
| 46 if (!result) | |
| 47 return E_INVALIDARG; | |
| 48 if (iid == IID_IUnknown || iid == IID_ITfTextEditSink) { | |
| 49 *result = static_cast<ITfTextEditSink*>(this); | |
| 50 } else if (iid == IID_ITfUIElementSink) { | |
| 51 *result = static_cast<ITfUIElementSink*>(this); | |
| 52 } else { | |
| 53 *result = NULL; | |
| 54 return E_NOINTERFACE; | |
| 55 } | |
| 56 AddRef(); | |
| 57 return S_OK; | |
| 58 } | |
| 59 | |
| 60 // ITfTextEditSink override. | |
| 61 virtual STDMETHODIMP OnEndEdit(ITfContext* context, | |
| 62 TfEditCookie read_only_cookie, | |
| 63 ITfEditRecord* edit_record) OVERRIDE { | |
| 64 if (!edit_record || !context) | |
| 65 return E_INVALIDARG; | |
| 66 if (!observer_) | |
| 67 return S_OK; | |
| 68 | |
| 69 // |edit_record| can be used to obtain updated ranges in terms of text | |
| 70 // contents and/or text attributes. Here we are interested only in text | |
| 71 // update so we use TF_GTP_INCL_TEXT and check if there is any range which | |
| 72 // contains updated text. | |
| 73 base::win::ScopedComPtr<IEnumTfRanges> ranges; | |
| 74 if (FAILED(edit_record->GetTextAndPropertyUpdates(TF_GTP_INCL_TEXT, | |
| 75 NULL, | |
| 76 0, | |
| 77 ranges.Receive()))) { | |
| 78 return S_OK; // don't care about failures. | |
| 79 } | |
| 80 | |
| 81 ULONG fetched_count = 0; | |
| 82 base::win::ScopedComPtr<ITfRange> range; | |
| 83 if (FAILED(ranges->Next(1, range.Receive(), &fetched_count))) | |
| 84 return S_OK; // don't care about failures. | |
| 85 | |
| 86 // |fetched_count| != 0 means there is at least one range that contains | |
| 87 // updated texts. | |
| 88 if (fetched_count != 0) | |
| 89 observer_->OnTextUpdated(); | |
| 90 return S_OK; | |
| 91 } | |
| 92 | |
| 93 // ITfUiElementSink override. | |
| 94 virtual STDMETHODIMP BeginUIElement(DWORD element_id, BOOL* is_show) { | |
| 95 if (is_show) | |
| 96 *is_show = TRUE; // without this the UI element will not be shown. | |
| 97 | |
| 98 if (!IsCandidateWindowInternal(element_id)) | |
| 99 return S_OK; | |
| 100 | |
| 101 std::pair<std::set<DWORD>::iterator, bool> insert_result = | |
| 102 open_candidate_window_ids_.insert(element_id); | |
| 103 | |
| 104 if (!observer_) | |
| 105 return S_OK; | |
| 106 | |
| 107 // Don't call if |element_id| is already handled. | |
| 108 if (!insert_result.second) | |
| 109 return S_OK; | |
| 110 | |
| 111 observer_->OnCandidateWindowCountChanged(open_candidate_window_ids_.size()); | |
| 112 | |
| 113 return S_OK; | |
| 114 } | |
| 115 | |
| 116 // ITfUiElementSink override. | |
| 117 virtual STDMETHODIMP UpdateUIElement(DWORD element_id) { | |
| 118 return S_OK; | |
| 119 } | |
| 120 | |
| 121 // ITfUiElementSink override. | |
| 122 virtual STDMETHODIMP EndUIElement(DWORD element_id) { | |
| 123 if (open_candidate_window_ids_.erase(element_id) == 0) | |
| 124 return S_OK; | |
| 125 | |
| 126 if (!observer_) | |
| 127 return S_OK; | |
| 128 | |
| 129 observer_->OnCandidateWindowCountChanged(open_candidate_window_ids_.size()); | |
| 130 | |
| 131 return S_OK; | |
| 132 } | |
| 133 | |
| 134 // TsfEventRouter override. | |
| 135 virtual void SetManager(ITfThreadMgr* manager, Observer* observer) OVERRIDE { | |
| 136 EnsureDeassociated(); | |
| 137 if (manager && observer) { | |
| 138 Associate(manager, observer); | |
| 139 } | |
| 140 } | |
| 141 | |
| 142 // TsfEventRouter override. | |
| 143 virtual bool IsImeComposing() OVERRIDE { | |
| 144 DCHECK(base::win::IsTsfAwareRequired()) | |
| 145 << "Do not call without TSF environment."; | |
| 146 if (!context_) | |
| 147 return false; | |
| 148 return IsImeComposingInternal(context_); | |
| 149 } | |
| 150 | |
| 151 private: | |
| 152 // Returns true if the given |context| is in composing. | |
| 153 static bool IsImeComposingInternal(ITfContext* context) { | |
| 154 DCHECK(base::win::IsTsfAwareRequired()) | |
| 155 << "Do not call without TSF environment."; | |
| 156 DCHECK(context); | |
| 157 base::win::ScopedComPtr<ITfContextComposition> context_composition; | |
| 158 if (FAILED(context_composition.QueryFrom(context))) | |
| 159 return false; | |
| 160 base::win::ScopedComPtr<IEnumITfCompositionView> enum_composition_view; | |
| 161 if (FAILED(context_composition->EnumCompositions( | |
| 162 enum_composition_view.Receive()))) | |
| 163 return false; | |
| 164 base::win::ScopedComPtr<ITfCompositionView> composition_view; | |
| 165 return enum_composition_view->Next(1, composition_view.Receive(), | |
| 166 NULL) == S_OK; | |
| 167 } | |
| 168 | |
| 169 // Returns true if the given |element_id| represents candidate window. | |
| 170 bool IsCandidateWindowInternal(DWORD element_id) { | |
| 171 DCHECK(ui_element_manager_.get()); | |
| 172 base::win::ScopedComPtr<ITfUIElement> ui_element; | |
| 173 if (FAILED(ui_element_manager_->GetUIElement(element_id, | |
| 174 ui_element.Receive()))) { | |
| 175 return false; | |
| 176 } | |
| 177 base::win::ScopedComPtr<ITfCandidateListUIElement> | |
| 178 candidate_list_ui_element; | |
| 179 return SUCCEEDED(candidate_list_ui_element.QueryFrom(ui_element)); | |
| 180 } | |
| 181 | |
| 182 // Associates this class with specified |manager|. | |
| 183 void Associate(ITfThreadMgr* thread_manager, Observer* observer) { | |
| 184 DCHECK(base::win::IsTsfAwareRequired()) | |
| 185 << "Do not call without TSF environment."; | |
| 186 DCHECK(thread_manager); | |
| 187 | |
| 188 base::win::ScopedComPtr<ITfDocumentMgr> document_manager; | |
| 189 if (FAILED(thread_manager->GetFocus(document_manager.Receive()))) | |
| 190 return; | |
| 191 | |
| 192 if (FAILED(document_manager->GetBase(context_.Receive()))) | |
| 193 return; | |
| 194 if (FAILED(context_source_.QueryFrom(context_))) | |
| 195 return; | |
| 196 context_source_->AdviseSink(IID_ITfTextEditSink, | |
| 197 static_cast<ITfTextEditSink*>(this), | |
| 198 &context_source_cookie_); | |
| 199 | |
| 200 if (FAILED(ui_element_manager_.QueryFrom(thread_manager))) | |
| 201 return; | |
| 202 if (FAILED(ui_source_.QueryFrom(ui_element_manager_))) | |
| 203 return; | |
| 204 ui_source_->AdviseSink(IID_ITfUIElementSink, | |
| 205 static_cast<ITfUIElementSink*>(this), | |
| 206 &ui_source_cookie_); | |
| 207 observer_ = observer; | |
| 208 } | |
| 209 | |
| 210 // Resets the association, this function is safe to call if there is no | |
| 211 // associated thread manager. | |
| 212 void EnsureDeassociated() { | |
| 213 DCHECK(base::win::IsTsfAwareRequired()) | |
| 214 << "Do not call without TSF environment."; | |
| 215 | |
| 216 context_.Release(); | |
| 217 | |
| 218 if (context_source_) { | |
| 219 context_source_->UnadviseSink(context_source_cookie_); | |
| 220 context_source_.Release(); | |
| 221 } | |
| 222 context_source_cookie_ = TF_INVALID_COOKIE; | |
| 223 | |
| 224 ui_element_manager_.Release(); | |
| 225 if (ui_source_) { | |
| 226 ui_source_->UnadviseSink(ui_source_cookie_); | |
| 227 ui_source_.Release(); | |
| 228 } | |
| 229 ui_source_cookie_ = TF_INVALID_COOKIE; | |
| 230 | |
| 231 observer_ = NULL; | |
| 232 } | |
| 233 | |
| 234 Observer* observer_; | |
| 235 | |
| 236 // A context associated with this class. | |
| 237 base::win::ScopedComPtr<ITfContext> context_; | |
| 238 | |
| 239 // The ITfSource associated with |context_|. | |
| 240 base::win::ScopedComPtr<ITfSource> context_source_; | |
| 241 | |
| 242 // The cookie for |context_source_|. | |
| 243 DWORD context_source_cookie_; | |
| 244 | |
| 245 // A UiElementMgr associated with this class. | |
| 246 base::win::ScopedComPtr<ITfUIElementMgr> ui_element_manager_; | |
| 247 | |
| 248 // The ITfSouce associated with |ui_element_manager_|. | |
| 249 base::win::ScopedComPtr<ITfSource> ui_source_; | |
| 250 | |
| 251 // The cookie for |ui_source_|. | |
| 252 DWORD ui_source_cookie_; | |
| 253 | |
| 254 // The set of currently opend candidate window ids. | |
| 255 std::set<DWORD> open_candidate_window_ids_; | |
| 256 | |
| 257 // The reference count of this instance. | |
| 258 volatile LONG ref_count_; | |
| 259 | |
| 260 DISALLOW_COPY_AND_ASSIGN(TsfEventRouterImpl); | |
| 261 }; | |
| 262 | |
| 263 /////////////////////////////////////////////////////////////////////////////// | |
| 264 // TsfEventRouter | |
| 265 | |
| 266 TsfEventRouter::TsfEventRouter() { | 18 TsfEventRouter::TsfEventRouter() { |
| 19 ui::win::CreateATLModuleIfNeeded(); | |
| 20 CComObject<TsfEventRouterDelegate>* delegate = NULL; | |
| 21 if (SUCCEEDED(CComObject<TsfEventRouterDelegate>::CreateInstance( | |
| 22 &delegate))) { | |
|
Peter Kasting
2012/10/24 20:48:11
I believe you have to explicitly AddRef() here, si
cpu_(ooo_6.6-7.5)
2012/10/25 00:43:29
Good catch. This is a bug. I can even guess the re
Seigo Nonaka
2012/10/25 14:37:16
I'm getting confused about what type is should be
| |
| 23 delegate_ = delegate; | |
| 24 } | |
| 267 } | 25 } |
| 268 | 26 |
| 269 TsfEventRouter::~TsfEventRouter() { | 27 TsfEventRouter::~TsfEventRouter() { |
| 270 } | 28 if (delegate_) |
| 271 | 29 delegate_->SetManager(NULL, NULL); |
| 272 TsfEventRouter* TsfEventRouter::Create() { | 30 } |
| 273 return new TsfEventRouterImpl(); | 31 |
| 274 } | 32 TsfEventRouter::TsfEventRouterDelegate::TsfEventRouterDelegate() |
| 275 | 33 : context_source_cookie_(TF_INVALID_COOKIE), |
| 34 ui_source_cookie_(TF_INVALID_COOKIE), | |
|
Peter Kasting
2012/10/24 20:48:11
Nit: Indent even
Seigo Nonaka
2012/10/25 14:37:16
Done.
| |
| 35 observer_(NULL) {} | |
| 36 | |
| 37 TsfEventRouter::TsfEventRouterDelegate::~TsfEventRouterDelegate() {} | |
| 38 | |
| 39 STDMETHODIMP TsfEventRouter::TsfEventRouterDelegate::OnEndEdit( | |
|
Peter Kasting
2012/10/24 20:48:11
Please separate all TsfEventRouterDelegate functio
Seigo Nonaka
2012/10/25 14:37:16
Done.
| |
| 40 ITfContext* context, | |
| 41 TfEditCookie read_only_cookie, | |
| 42 ITfEditRecord* edit_record) { | |
| 43 if (!edit_record || !context) | |
| 44 return E_INVALIDARG; | |
| 45 if (!observer_) | |
| 46 return S_OK; | |
| 47 | |
| 48 // |edit_record| can be used to obtain updated ranges in terms of text | |
| 49 // contents and/or text attributes. Here we are interested only in text update | |
| 50 // so we use TF_GTP_INCL_TEXT and check if there is any range which contains | |
| 51 // updated text. | |
| 52 base::win::ScopedComPtr<IEnumTfRanges> ranges; | |
| 53 if (FAILED(edit_record->GetTextAndPropertyUpdates(TF_GTP_INCL_TEXT, NULL, 0, | |
| 54 ranges.Receive()))) { | |
|
Peter Kasting
2012/10/24 20:48:11
Nit: {} unnecessary when body is one line (3 place
Seigo Nonaka
2012/10/25 14:37:16
Done.
| |
| 55 return S_OK; // don't care about failures. | |
|
Peter Kasting
2012/10/24 20:48:11
Nit: Capitalize first word (3 places)
Seigo Nonaka
2012/10/25 14:37:16
Done.
| |
| 56 } | |
| 57 | |
| 58 ULONG fetched_count = 0; | |
| 59 base::win::ScopedComPtr<ITfRange> range; | |
| 60 if (FAILED(ranges->Next(1, range.Receive(), &fetched_count))) | |
| 61 return S_OK; // don't care about failures. | |
| 62 | |
| 63 // |fetched_count| != 0 means there is at least one range that contains | |
| 64 // updated texts. | |
|
Peter Kasting
2012/10/24 20:48:11
Nit: texts -> text
Seigo Nonaka
2012/10/25 14:37:16
Done.
| |
| 65 if (fetched_count != 0) | |
| 66 observer_->OnTextUpdated(); | |
| 67 return S_OK; | |
| 68 } | |
| 69 | |
| 70 STDMETHODIMP TsfEventRouter::TsfEventRouterDelegate::BeginUIElement( | |
| 71 DWORD element_id, | |
| 72 BOOL* is_show) { | |
| 73 if (is_show) | |
| 74 *is_show = TRUE; // without this the UI element will not be shown. | |
| 75 | |
| 76 if (!IsCandidateWindowInternal(element_id)) | |
| 77 return S_OK; | |
| 78 | |
| 79 std::pair<std::set<DWORD>::iterator, bool> insert_result = | |
| 80 open_candidate_window_ids_.insert(element_id); | |
| 81 | |
| 82 if (!observer_) | |
|
Peter Kasting
2012/10/24 20:48:11
Nit: Simpler:
if (observer_ && insert_result.se
Seigo Nonaka
2012/10/25 14:37:16
Done.
| |
| 83 return S_OK; | |
| 84 | |
| 85 // Don't call if |element_id| is already handled. | |
| 86 if (!insert_result.second) | |
| 87 return S_OK; | |
| 88 | |
| 89 observer_->OnCandidateWindowCountChanged(open_candidate_window_ids_.size()); | |
| 90 | |
| 91 return S_OK; | |
| 92 } | |
| 93 | |
| 94 STDMETHODIMP TsfEventRouter::TsfEventRouterDelegate::UpdateUIElement( | |
| 95 DWORD element_id) { | |
| 96 return S_OK; | |
| 97 } | |
| 98 | |
| 99 STDMETHODIMP TsfEventRouter::TsfEventRouterDelegate::EndUIElement( | |
| 100 DWORD element_id) { | |
| 101 if (open_candidate_window_ids_.erase(element_id) == 0) | |
|
Peter Kasting
2012/10/24 20:48:11
Nit: Simpler:
if ((open_candidate_window_ids_.e
Seigo Nonaka
2012/10/25 14:37:16
Done.
| |
| 102 return S_OK; | |
| 103 | |
| 104 if (!observer_) | |
| 105 return S_OK; | |
| 106 | |
| 107 observer_->OnCandidateWindowCountChanged(open_candidate_window_ids_.size()); | |
| 108 | |
| 109 return S_OK; | |
| 110 } | |
| 111 | |
| 112 void TsfEventRouter::TsfEventRouterDelegate::SetManager(ITfThreadMgr* manager, | |
| 113 Observer* observer) { | |
| 114 EnsureDeassociated(); | |
| 115 if (manager && observer) { | |
| 116 Associate(manager, observer); | |
| 117 } | |
| 118 } | |
| 119 | |
| 120 void TsfEventRouter::SetManager(ITfThreadMgr* manager, Observer* observer) { | |
| 121 delegate_->SetManager(manager, observer); | |
| 122 } | |
| 123 | |
| 124 bool TsfEventRouter::IsImeComposing() { | |
| 125 return delegate_->IsImeComposing(); | |
| 126 } | |
| 127 | |
| 128 bool TsfEventRouter::TsfEventRouterDelegate::IsImeComposing() { | |
| 129 DCHECK(base::win::IsTsfAwareRequired()) | |
|
Peter Kasting
2012/10/24 20:48:11
Seems like this DCHECK belongs in the constructor
Seigo Nonaka
2012/10/25 14:37:16
Yes, removed.
On 2012/10/24 20:48:11, Peter Kastin
| |
| 130 << "Do not call without TSF environment."; | |
| 131 if (!context_) | |
|
Peter Kasting
2012/10/24 20:48:11
Nit: Simpler:
return context_ && IsImeComposing
Seigo Nonaka
2012/10/25 14:37:16
Done.
| |
| 132 return false; | |
| 133 return IsImeComposingInternal(context_); | |
| 134 } | |
| 135 | |
| 136 // static | |
| 137 bool TsfEventRouter::TsfEventRouterDelegate::IsImeComposingInternal( | |
| 138 ITfContext* context) { | |
| 139 DCHECK(base::win::IsTsfAwareRequired()) | |
| 140 << "Do not call without TSF environment."; | |
| 141 DCHECK(context); | |
|
Peter Kasting
2012/10/24 20:48:11
Nit: Be consistent about whether precondition DCHE
Seigo Nonaka
2012/10/25 14:37:16
Sure, removing all blank line after DCHECK.
On 201
| |
| 142 base::win::ScopedComPtr<ITfContextComposition> context_composition; | |
| 143 if (FAILED(context_composition.QueryFrom(context))) | |
| 144 return false; | |
| 145 base::win::ScopedComPtr<IEnumITfCompositionView> enum_composition_view; | |
| 146 if (FAILED(context_composition->EnumCompositions( | |
| 147 enum_composition_view.Receive()))) | |
|
Peter Kasting
2012/10/24 20:48:11
Nit: indent 4, not 8
Seigo Nonaka
2012/10/25 14:37:16
Done.
| |
| 148 return false; | |
| 149 base::win::ScopedComPtr<ITfCompositionView> composition_view; | |
| 150 return | |
|
Peter Kasting
2012/10/24 20:48:11
Nit: I suggest breaking as:
return enum_composi
Seigo Nonaka
2012/10/25 14:37:16
Sure, done.
On 2012/10/24 20:48:11, Peter Kasting
| |
| 151 enum_composition_view->Next(1, composition_view.Receive(), NULL) == S_OK; | |
| 152 } | |
| 153 | |
| 154 bool TsfEventRouter::TsfEventRouterDelegate::IsCandidateWindowInternal( | |
| 155 DWORD element_id) { | |
| 156 DCHECK(ui_element_manager_.get()); | |
| 157 base::win::ScopedComPtr<ITfUIElement> ui_element; | |
| 158 if (FAILED(ui_element_manager_->GetUIElement(element_id, | |
| 159 ui_element.Receive()))) { | |
| 160 return false; | |
| 161 } | |
| 162 base::win::ScopedComPtr<ITfCandidateListUIElement> candidate_list_ui_element; | |
| 163 return SUCCEEDED(candidate_list_ui_element.QueryFrom(ui_element)); | |
| 164 } | |
| 165 | |
| 166 void TsfEventRouter::TsfEventRouterDelegate::Associate( | |
| 167 ITfThreadMgr* thread_manager, | |
| 168 Observer* observer) { | |
| 169 DCHECK(base::win::IsTsfAwareRequired()) | |
| 170 << "Do not call without TSF environment."; | |
| 171 DCHECK(thread_manager); | |
| 172 | |
| 173 base::win::ScopedComPtr<ITfDocumentMgr> document_manager; | |
| 174 if (FAILED(thread_manager->GetFocus(document_manager.Receive()))) | |
|
Peter Kasting
2012/10/24 20:48:11
Nit: Can combine multiple FAILED() calls together
Seigo Nonaka
2012/10/25 14:37:16
Done.
| |
| 175 return; | |
| 176 | |
| 177 if (FAILED(document_manager->GetBase(context_.Receive()))) | |
| 178 return; | |
| 179 if (FAILED(context_source_.QueryFrom(context_))) | |
| 180 return; | |
| 181 context_source_->AdviseSink(IID_ITfTextEditSink, | |
| 182 static_cast<ITfTextEditSink*>(this), | |
| 183 &context_source_cookie_); | |
| 184 | |
| 185 if (FAILED(ui_element_manager_.QueryFrom(thread_manager))) | |
| 186 return; | |
| 187 if (FAILED(ui_source_.QueryFrom(ui_element_manager_))) | |
| 188 return; | |
| 189 ui_source_->AdviseSink(IID_ITfUIElementSink, | |
| 190 static_cast<ITfUIElementSink*>(this), | |
| 191 &ui_source_cookie_); | |
| 192 observer_ = observer; | |
| 193 } | |
| 194 | |
| 195 void TsfEventRouter::TsfEventRouterDelegate::EnsureDeassociated() { | |
| 196 DCHECK(base::win::IsTsfAwareRequired()) | |
| 197 << "Do not call without TSF environment."; | |
| 198 | |
| 199 context_.Release(); | |
| 200 | |
| 201 if (context_source_) { | |
| 202 context_source_->UnadviseSink(context_source_cookie_); | |
| 203 context_source_.Release(); | |
| 204 } | |
| 205 context_source_cookie_ = TF_INVALID_COOKIE; | |
| 206 | |
| 207 ui_element_manager_.Release(); | |
| 208 if (ui_source_) { | |
| 209 ui_source_->UnadviseSink(ui_source_cookie_); | |
| 210 ui_source_.Release(); | |
| 211 } | |
| 212 ui_source_cookie_ = TF_INVALID_COOKIE; | |
| 213 | |
| 214 observer_ = NULL; | |
| 215 } | |
| 276 } // namespace ui | 216 } // namespace ui |
| OLD | NEW |