|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=2 sw=2 et tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "ContentEventHandler.h" |
|
8 #include "nsContentUtils.h" |
|
9 #include "nsIContent.h" |
|
10 #include "nsIEditor.h" |
|
11 #include "nsIPresShell.h" |
|
12 #include "nsPresContext.h" |
|
13 #include "mozilla/EventDispatcher.h" |
|
14 #include "mozilla/IMEStateManager.h" |
|
15 #include "mozilla/MiscEvents.h" |
|
16 #include "mozilla/TextComposition.h" |
|
17 #include "mozilla/TextEvents.h" |
|
18 |
|
19 using namespace mozilla::widget; |
|
20 |
|
21 namespace mozilla { |
|
22 |
|
23 /****************************************************************************** |
|
24 * TextComposition |
|
25 ******************************************************************************/ |
|
26 |
|
27 TextComposition::TextComposition(nsPresContext* aPresContext, |
|
28 nsINode* aNode, |
|
29 WidgetGUIEvent* aEvent) |
|
30 : mPresContext(aPresContext) |
|
31 , mNode(aNode) |
|
32 , mNativeContext(aEvent->widget->GetInputContext().mNativeIMEContext) |
|
33 , mCompositionStartOffset(0) |
|
34 , mCompositionTargetOffset(0) |
|
35 , mIsSynthesizedForTests(aEvent->mFlags.mIsSynthesizedForTests) |
|
36 , mIsComposing(false) |
|
37 , mIsEditorHandlingEvent(false) |
|
38 { |
|
39 } |
|
40 |
|
41 void |
|
42 TextComposition::Destroy() |
|
43 { |
|
44 mPresContext = nullptr; |
|
45 mNode = nullptr; |
|
46 // TODO: If the editor is still alive and this is held by it, we should tell |
|
47 // this being destroyed for cleaning up the stuff. |
|
48 } |
|
49 |
|
50 bool |
|
51 TextComposition::MatchesNativeContext(nsIWidget* aWidget) const |
|
52 { |
|
53 return mNativeContext == aWidget->GetInputContext().mNativeIMEContext; |
|
54 } |
|
55 |
|
56 void |
|
57 TextComposition::DispatchEvent(WidgetGUIEvent* aEvent, |
|
58 nsEventStatus* aStatus, |
|
59 EventDispatchingCallback* aCallBack) |
|
60 { |
|
61 if (aEvent->message == NS_COMPOSITION_UPDATE) { |
|
62 mLastData = aEvent->AsCompositionEvent()->data; |
|
63 } |
|
64 |
|
65 EventDispatcher::Dispatch(mNode, mPresContext, |
|
66 aEvent, nullptr, aStatus, aCallBack); |
|
67 |
|
68 if (!mPresContext) { |
|
69 return; |
|
70 } |
|
71 |
|
72 // Emulate editor behavior of text event handler if no editor handles |
|
73 // composition/text events. |
|
74 if (aEvent->message == NS_TEXT_TEXT && !HasEditor()) { |
|
75 EditorWillHandleTextEvent(aEvent->AsTextEvent()); |
|
76 EditorDidHandleTextEvent(); |
|
77 } |
|
78 |
|
79 #ifdef DEBUG |
|
80 else if (aEvent->message == NS_COMPOSITION_END) { |
|
81 MOZ_ASSERT(!mIsComposing, "Why is the editor still composing?"); |
|
82 MOZ_ASSERT(!HasEditor(), "Why does the editor still keep to hold this?"); |
|
83 } |
|
84 #endif // #ifdef DEBUG |
|
85 |
|
86 // Notify composition update to widget if possible |
|
87 NotityUpdateComposition(aEvent); |
|
88 } |
|
89 |
|
90 void |
|
91 TextComposition::NotityUpdateComposition(WidgetGUIEvent* aEvent) |
|
92 { |
|
93 nsEventStatus status; |
|
94 |
|
95 // When compositon start, notify the rect of first offset character. |
|
96 // When not compositon start, notify the rect of selected composition |
|
97 // string if text event. |
|
98 if (aEvent->message == NS_COMPOSITION_START) { |
|
99 nsCOMPtr<nsIWidget> widget = mPresContext->GetRootWidget(); |
|
100 // Update composition start offset |
|
101 WidgetQueryContentEvent selectedTextEvent(true, |
|
102 NS_QUERY_SELECTED_TEXT, |
|
103 widget); |
|
104 widget->DispatchEvent(&selectedTextEvent, status); |
|
105 if (selectedTextEvent.mSucceeded) { |
|
106 mCompositionStartOffset = selectedTextEvent.mReply.mOffset; |
|
107 } else { |
|
108 // Unknown offset |
|
109 NS_WARNING("Cannot get start offset of IME composition"); |
|
110 mCompositionStartOffset = 0; |
|
111 } |
|
112 mCompositionTargetOffset = mCompositionStartOffset; |
|
113 } else if (aEvent->eventStructType != NS_TEXT_EVENT) { |
|
114 return; |
|
115 } else { |
|
116 mCompositionTargetOffset = |
|
117 mCompositionStartOffset + aEvent->AsTextEvent()->TargetClauseOffset(); |
|
118 } |
|
119 |
|
120 NotifyIME(NOTIFY_IME_OF_COMPOSITION_UPDATE); |
|
121 } |
|
122 |
|
123 void |
|
124 TextComposition::DispatchCompositionEventRunnable(uint32_t aEventMessage, |
|
125 const nsAString& aData) |
|
126 { |
|
127 nsContentUtils::AddScriptRunner( |
|
128 new CompositionEventDispatcher(mPresContext, mNode, |
|
129 aEventMessage, aData)); |
|
130 } |
|
131 |
|
132 void |
|
133 TextComposition::SynthesizeCommit(bool aDiscard) |
|
134 { |
|
135 nsRefPtr<TextComposition> kungFuDeathGrip(this); |
|
136 nsAutoString data(aDiscard ? EmptyString() : mLastData); |
|
137 if (mLastData != data) { |
|
138 DispatchCompositionEventRunnable(NS_COMPOSITION_UPDATE, data); |
|
139 DispatchCompositionEventRunnable(NS_TEXT_TEXT, data); |
|
140 } |
|
141 DispatchCompositionEventRunnable(NS_COMPOSITION_END, data); |
|
142 } |
|
143 |
|
144 nsresult |
|
145 TextComposition::NotifyIME(IMEMessage aMessage) |
|
146 { |
|
147 NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE); |
|
148 return IMEStateManager::NotifyIME(aMessage, mPresContext); |
|
149 } |
|
150 |
|
151 void |
|
152 TextComposition::EditorWillHandleTextEvent(const WidgetTextEvent* aTextEvent) |
|
153 { |
|
154 mIsComposing = aTextEvent->IsComposing(); |
|
155 mRanges = aTextEvent->mRanges; |
|
156 mIsEditorHandlingEvent = true; |
|
157 |
|
158 MOZ_ASSERT(mLastData == aTextEvent->theText, |
|
159 "The text of a text event must be same as previous data attribute value " |
|
160 "of the latest compositionupdate event"); |
|
161 } |
|
162 |
|
163 void |
|
164 TextComposition::EditorDidHandleTextEvent() |
|
165 { |
|
166 mString = mLastData; |
|
167 mIsEditorHandlingEvent = false; |
|
168 } |
|
169 |
|
170 void |
|
171 TextComposition::StartHandlingComposition(nsIEditor* aEditor) |
|
172 { |
|
173 MOZ_ASSERT(!HasEditor(), "There is a handling editor already"); |
|
174 mEditorWeak = do_GetWeakReference(aEditor); |
|
175 } |
|
176 |
|
177 void |
|
178 TextComposition::EndHandlingComposition(nsIEditor* aEditor) |
|
179 { |
|
180 #ifdef DEBUG |
|
181 nsCOMPtr<nsIEditor> editor = GetEditor(); |
|
182 MOZ_ASSERT(editor == aEditor, "Another editor handled the composition?"); |
|
183 #endif // #ifdef DEBUG |
|
184 mEditorWeak = nullptr; |
|
185 } |
|
186 |
|
187 already_AddRefed<nsIEditor> |
|
188 TextComposition::GetEditor() const |
|
189 { |
|
190 nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditorWeak); |
|
191 return editor.forget(); |
|
192 } |
|
193 |
|
194 bool |
|
195 TextComposition::HasEditor() const |
|
196 { |
|
197 nsCOMPtr<nsIEditor> editor = GetEditor(); |
|
198 return !!editor; |
|
199 } |
|
200 |
|
201 /****************************************************************************** |
|
202 * TextComposition::CompositionEventDispatcher |
|
203 ******************************************************************************/ |
|
204 |
|
205 TextComposition::CompositionEventDispatcher::CompositionEventDispatcher( |
|
206 nsPresContext* aPresContext, |
|
207 nsINode* aEventTarget, |
|
208 uint32_t aEventMessage, |
|
209 const nsAString& aData) : |
|
210 mPresContext(aPresContext), mEventTarget(aEventTarget), |
|
211 mEventMessage(aEventMessage), mData(aData) |
|
212 { |
|
213 mWidget = mPresContext->GetRootWidget(); |
|
214 } |
|
215 |
|
216 NS_IMETHODIMP |
|
217 TextComposition::CompositionEventDispatcher::Run() |
|
218 { |
|
219 if (!mPresContext->GetPresShell() || |
|
220 mPresContext->GetPresShell()->IsDestroying()) { |
|
221 return NS_OK; // cannot dispatch any events anymore |
|
222 } |
|
223 |
|
224 nsEventStatus status = nsEventStatus_eIgnore; |
|
225 switch (mEventMessage) { |
|
226 case NS_COMPOSITION_START: { |
|
227 WidgetCompositionEvent compStart(true, NS_COMPOSITION_START, mWidget); |
|
228 WidgetQueryContentEvent selectedText(true, NS_QUERY_SELECTED_TEXT, |
|
229 mWidget); |
|
230 ContentEventHandler handler(mPresContext); |
|
231 handler.OnQuerySelectedText(&selectedText); |
|
232 NS_ASSERTION(selectedText.mSucceeded, "Failed to get selected text"); |
|
233 compStart.data = selectedText.mReply.mString; |
|
234 IMEStateManager::DispatchCompositionEvent(mEventTarget, mPresContext, |
|
235 &compStart, &status, nullptr); |
|
236 break; |
|
237 } |
|
238 case NS_COMPOSITION_UPDATE: |
|
239 case NS_COMPOSITION_END: { |
|
240 WidgetCompositionEvent compEvent(true, mEventMessage, mWidget); |
|
241 compEvent.data = mData; |
|
242 IMEStateManager::DispatchCompositionEvent(mEventTarget, mPresContext, |
|
243 &compEvent, &status, nullptr); |
|
244 break; |
|
245 } |
|
246 case NS_TEXT_TEXT: { |
|
247 WidgetTextEvent textEvent(true, NS_TEXT_TEXT, mWidget); |
|
248 textEvent.theText = mData; |
|
249 IMEStateManager::DispatchCompositionEvent(mEventTarget, mPresContext, |
|
250 &textEvent, &status, nullptr); |
|
251 break; |
|
252 } |
|
253 default: |
|
254 MOZ_CRASH("Unsupported event"); |
|
255 } |
|
256 return NS_OK; |
|
257 } |
|
258 |
|
259 /****************************************************************************** |
|
260 * TextCompositionArray |
|
261 ******************************************************************************/ |
|
262 |
|
263 TextCompositionArray::index_type |
|
264 TextCompositionArray::IndexOf(nsIWidget* aWidget) |
|
265 { |
|
266 for (index_type i = Length(); i > 0; --i) { |
|
267 if (ElementAt(i - 1)->MatchesNativeContext(aWidget)) { |
|
268 return i - 1; |
|
269 } |
|
270 } |
|
271 return NoIndex; |
|
272 } |
|
273 |
|
274 TextCompositionArray::index_type |
|
275 TextCompositionArray::IndexOf(nsPresContext* aPresContext) |
|
276 { |
|
277 for (index_type i = Length(); i > 0; --i) { |
|
278 if (ElementAt(i - 1)->GetPresContext() == aPresContext) { |
|
279 return i - 1; |
|
280 } |
|
281 } |
|
282 return NoIndex; |
|
283 } |
|
284 |
|
285 TextCompositionArray::index_type |
|
286 TextCompositionArray::IndexOf(nsPresContext* aPresContext, |
|
287 nsINode* aNode) |
|
288 { |
|
289 index_type index = IndexOf(aPresContext); |
|
290 if (index == NoIndex) { |
|
291 return NoIndex; |
|
292 } |
|
293 nsINode* node = ElementAt(index)->GetEventTargetNode(); |
|
294 return node == aNode ? index : NoIndex; |
|
295 } |
|
296 |
|
297 TextComposition* |
|
298 TextCompositionArray::GetCompositionFor(nsIWidget* aWidget) |
|
299 { |
|
300 index_type i = IndexOf(aWidget); |
|
301 return i != NoIndex ? ElementAt(i) : nullptr; |
|
302 } |
|
303 |
|
304 TextComposition* |
|
305 TextCompositionArray::GetCompositionFor(nsPresContext* aPresContext, |
|
306 nsINode* aNode) |
|
307 { |
|
308 index_type i = IndexOf(aPresContext, aNode); |
|
309 return i != NoIndex ? ElementAt(i) : nullptr; |
|
310 } |
|
311 |
|
312 TextComposition* |
|
313 TextCompositionArray::GetCompositionInContent(nsPresContext* aPresContext, |
|
314 nsIContent* aContent) |
|
315 { |
|
316 // There should be only one composition per content object. |
|
317 for (index_type i = Length(); i > 0; --i) { |
|
318 nsINode* node = ElementAt(i - 1)->GetEventTargetNode(); |
|
319 if (node && nsContentUtils::ContentIsDescendantOf(node, aContent)) { |
|
320 return ElementAt(i - 1); |
|
321 } |
|
322 } |
|
323 return nullptr; |
|
324 } |
|
325 |
|
326 } // namespace mozilla |