Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 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 : #ifndef mozilla_TextComposition_h
8 : #define mozilla_TextComposition_h
9 :
10 : #include "nsCOMPtr.h"
11 : #include "nsINode.h"
12 : #include "nsIWeakReference.h"
13 : #include "nsIWidget.h"
14 : #include "nsTArray.h"
15 : #include "nsThreadUtils.h"
16 : #include "nsPresContext.h"
17 : #include "mozilla/Attributes.h"
18 : #include "mozilla/EventForwards.h"
19 : #include "mozilla/TextRange.h"
20 : #include "mozilla/dom/TabParent.h"
21 :
22 : namespace mozilla {
23 :
24 : class EditorBase;
25 : class EventDispatchingCallback;
26 : class IMEStateManager;
27 :
28 : /**
29 : * TextComposition represents a text composition. This class stores the
30 : * composition event target and its presContext. At dispatching the event via
31 : * this class, the instances use the stored event target.
32 : */
33 :
34 : class TextComposition final
35 : {
36 : friend class IMEStateManager;
37 :
38 0 : NS_INLINE_DECL_REFCOUNTING(TextComposition)
39 :
40 : public:
41 : typedef dom::TabParent TabParent;
42 :
43 0 : static bool IsHandlingSelectionEvent() { return sHandlingSelectionEvent; }
44 :
45 : TextComposition(nsPresContext* aPresContext,
46 : nsINode* aNode,
47 : TabParent* aTabParent,
48 : WidgetCompositionEvent* aCompositionEvent);
49 :
50 0 : bool Destroyed() const { return !mPresContext; }
51 0 : nsPresContext* GetPresContext() const { return mPresContext; }
52 0 : nsINode* GetEventTargetNode() const { return mNode; }
53 : // The latest CompositionEvent.data value except compositionstart event.
54 : // This value is modified at dispatching compositionupdate.
55 0 : const nsString& LastData() const { return mLastData; }
56 : // The composition string which is already handled by the focused editor.
57 : // I.e., this value must be same as the composition string on the focused
58 : // editor. This value is modified at a call of
59 : // EditorDidHandleCompositionChangeEvent().
60 : // Note that mString and mLastData are different between dispatcing
61 : // compositionupdate and compositionchange event handled by focused editor.
62 0 : const nsString& String() const { return mString; }
63 : // The latest clauses range of the composition string.
64 : // During compositionupdate event, GetRanges() returns old ranges.
65 : // So if getting on compositionupdate, Use GetLastRange instead of GetRange().
66 : TextRangeArray* GetLastRanges() const { return mLastRanges; }
67 : // Returns the clauses and/or caret range of the composition string.
68 : // This is modified at a call of EditorWillHandleCompositionChangeEvent().
69 : // This may return null if there is no clauses and caret.
70 : // XXX We should return |const TextRangeArray*| here, but it causes compile
71 : // error due to inaccessible Release() method.
72 0 : TextRangeArray* GetRanges() const { return mRanges; }
73 : // Returns the widget which is proper to call NotifyIME().
74 0 : nsIWidget* GetWidget() const
75 : {
76 0 : return mPresContext ? mPresContext->GetRootWidget() : nullptr;
77 : }
78 : // Returns the tab parent which has this composition in its remote process.
79 0 : TabParent* GetTabParent() const
80 : {
81 0 : return mTabParent;
82 : }
83 : // Returns true if the composition is started with synthesized event which
84 : // came from nsDOMWindowUtils.
85 0 : bool IsSynthesizedForTests() const { return mIsSynthesizedForTests; }
86 :
87 0 : const widget::NativeIMEContext& GetNativeIMEContext() const
88 : {
89 0 : return mNativeContext;
90 : }
91 :
92 : /**
93 : * This is called when IMEStateManager stops managing the instance.
94 : */
95 : void Destroy();
96 :
97 : /**
98 : * Request to commit (or cancel) the composition to IME. This method should
99 : * be called only by IMEStateManager::NotifyIME().
100 : */
101 : nsresult RequestToCommit(nsIWidget* aWidget, bool aDiscard);
102 :
103 : /**
104 : * Send a notification to IME. It depends on the IME or platform spec what
105 : * will occur (or not occur).
106 : */
107 : nsresult NotifyIME(widget::IMEMessage aMessage);
108 :
109 : /**
110 : * the offset of first composition string
111 : */
112 0 : uint32_t NativeOffsetOfStartComposition() const
113 : {
114 0 : return mCompositionStartOffset;
115 : }
116 :
117 : /**
118 : * the offset of first selected clause or start of composition
119 : */
120 : uint32_t NativeOffsetOfTargetClause() const
121 : {
122 : return mCompositionStartOffset + mTargetClauseOffsetInComposition;
123 : }
124 :
125 : /**
126 : * Returns true if there is non-empty composition string and it's not fixed.
127 : * Otherwise, false.
128 : */
129 0 : bool IsComposing() const { return mIsComposing; }
130 :
131 : /**
132 : * Returns true while editor is handling an event which is modifying the
133 : * composition string.
134 : */
135 0 : bool IsEditorHandlingEvent() const
136 : {
137 0 : return mIsEditorHandlingEvent;
138 : }
139 :
140 : /**
141 : * StartHandlingComposition() and EndHandlingComposition() are called by
142 : * editor when it holds a TextComposition instance and release it.
143 : */
144 : void StartHandlingComposition(EditorBase* aEditorBase);
145 : void EndHandlingComposition(EditorBase* aEditorBase);
146 :
147 : /**
148 : * OnEditorDestroyed() is called when the editor is destroyed but there is
149 : * active composition.
150 : */
151 : void OnEditorDestroyed();
152 :
153 : /**
154 : * CompositionChangeEventHandlingMarker class should be created at starting
155 : * to handle text event in focused editor. This calls
156 : * EditorWillHandleCompositionChangeEvent() and
157 : * EditorDidHandleCompositionChangeEvent() automatically.
158 : */
159 : class MOZ_STACK_CLASS CompositionChangeEventHandlingMarker
160 : {
161 : public:
162 0 : CompositionChangeEventHandlingMarker(
163 : TextComposition* aComposition,
164 : const WidgetCompositionEvent* aCompositionChangeEvent)
165 0 : : mComposition(aComposition)
166 : {
167 0 : mComposition->EditorWillHandleCompositionChangeEvent(
168 0 : aCompositionChangeEvent);
169 0 : }
170 :
171 0 : ~CompositionChangeEventHandlingMarker()
172 0 : {
173 0 : mComposition->EditorDidHandleCompositionChangeEvent();
174 0 : }
175 :
176 : private:
177 : RefPtr<TextComposition> mComposition;
178 : CompositionChangeEventHandlingMarker();
179 : CompositionChangeEventHandlingMarker(
180 : const CompositionChangeEventHandlingMarker& aOther);
181 : };
182 :
183 : private:
184 : // Private destructor, to discourage deletion outside of Release():
185 0 : ~TextComposition()
186 0 : {
187 : // WARNING: mPresContext may be destroying, so, be careful if you touch it.
188 0 : }
189 :
190 : // sHandlingSelectionEvent is true while TextComposition sends a selection
191 : // event to ContentEventHandler.
192 : static bool sHandlingSelectionEvent;
193 :
194 : // This class holds nsPresContext weak. This instance shouldn't block
195 : // destroying it. When the presContext is being destroyed, it's notified to
196 : // IMEStateManager::OnDestroyPresContext(), and then, it destroy
197 : // this instance.
198 : nsPresContext* mPresContext;
199 : nsCOMPtr<nsINode> mNode;
200 : RefPtr<TabParent> mTabParent;
201 :
202 : // This is the clause and caret range information which is managed by
203 : // the focused editor. This may be null if there is no clauses or caret.
204 : RefPtr<TextRangeArray> mRanges;
205 : // Same as mRange, but mRange will have old data during compositionupdate.
206 : // So this will be valied during compositionupdate.
207 : RefPtr<TextRangeArray> mLastRanges;
208 :
209 : // mNativeContext stores a opaque pointer. This works as the "ID" for this
210 : // composition. Don't access the instance, it may not be available.
211 : widget::NativeIMEContext mNativeContext;
212 :
213 : // mEditorBaseWeak is a weak reference to the focused editor handling
214 : // composition.
215 : nsWeakPtr mEditorBaseWeak;
216 :
217 : // mLastData stores the data attribute of the latest composition event (except
218 : // the compositionstart event).
219 : nsString mLastData;
220 :
221 : // mString stores the composition text which has been handled by the focused
222 : // editor.
223 : nsString mString;
224 :
225 : // Offset of the composition string from start of the editor
226 : uint32_t mCompositionStartOffset;
227 : // Offset of the selected clause of the composition string from
228 : // mCompositionStartOffset
229 : uint32_t mTargetClauseOffsetInComposition;
230 :
231 : // See the comment for IsSynthesizedForTests().
232 : bool mIsSynthesizedForTests;
233 :
234 : // See the comment for IsComposing().
235 : bool mIsComposing;
236 :
237 : // mIsEditorHandlingEvent is true while editor is modifying the composition
238 : // string.
239 : bool mIsEditorHandlingEvent;
240 :
241 : // mIsRequestingCommit or mIsRequestingCancel is true *only* while we're
242 : // requesting commit or canceling the composition. In other words, while
243 : // one of these values is true, we're handling the request.
244 : bool mIsRequestingCommit;
245 : bool mIsRequestingCancel;
246 :
247 : // mRequestedToCommitOrCancel is true *after* we requested IME to commit or
248 : // cancel the composition. In other words, we already requested of IME that
249 : // it commits or cancels current composition.
250 : // NOTE: Before this is set true, both mIsRequestingCommit and
251 : // mIsRequestingCancel are set false.
252 : bool mRequestedToCommitOrCancel;
253 :
254 : // mWasNativeCompositionEndEventDiscarded is true if this composition was
255 : // requested commit or cancel itself but native compositionend event is
256 : // discarded by PresShell due to not safe to dispatch events.
257 : bool mWasNativeCompositionEndEventDiscarded;
258 :
259 : // Allow control characters appear in composition string.
260 : // When this is false, control characters except
261 : // CHARACTER TABULATION (horizontal tab) are removed from
262 : // both composition string and data attribute of compositionupdate
263 : // and compositionend events.
264 : bool mAllowControlCharacters;
265 :
266 : // mWasCompositionStringEmpty is true if the composition string was empty
267 : // when DispatchCompositionEvent() is called.
268 : bool mWasCompositionStringEmpty;
269 :
270 : // Hide the default constructor and copy constructor.
271 : TextComposition()
272 : : mPresContext(nullptr)
273 : , mNativeContext(nullptr)
274 : , mCompositionStartOffset(0)
275 : , mTargetClauseOffsetInComposition(0)
276 : , mIsSynthesizedForTests(false)
277 : , mIsComposing(false)
278 : , mIsEditorHandlingEvent(false)
279 : , mIsRequestingCommit(false)
280 : , mIsRequestingCancel(false)
281 : , mRequestedToCommitOrCancel(false)
282 : , mWasNativeCompositionEndEventDiscarded(false)
283 : , mAllowControlCharacters(false)
284 : , mWasCompositionStringEmpty(true)
285 : {}
286 : TextComposition(const TextComposition& aOther);
287 :
288 : /**
289 : * GetEditorBase() returns EditorBase pointer of mEditorBaseWeak.
290 : */
291 : already_AddRefed<EditorBase> GetEditorBase() const;
292 :
293 : /**
294 : * HasEditor() returns true if mEditorBaseWeak holds EditorBase instance
295 : * which is alive. Otherwise, false.
296 : */
297 : bool HasEditor() const;
298 :
299 : /**
300 : * EditorWillHandleCompositionChangeEvent() must be called before the focused
301 : * editor handles the compositionchange event.
302 : */
303 : void EditorWillHandleCompositionChangeEvent(
304 : const WidgetCompositionEvent* aCompositionChangeEvent);
305 :
306 : /**
307 : * EditorDidHandleCompositionChangeEvent() must be called after the focused
308 : * editor handles a compositionchange event.
309 : */
310 : void EditorDidHandleCompositionChangeEvent();
311 :
312 : /**
313 : * IsValidStateForComposition() returns true if it's safe to dispatch an event
314 : * to the DOM tree. Otherwise, false.
315 : * WARNING: This doesn't check script blocker state. It should be checked
316 : * before dispatching the first event.
317 : */
318 : bool IsValidStateForComposition(nsIWidget* aWidget) const;
319 :
320 : /**
321 : * DispatchCompositionEvent() dispatches the aCompositionEvent to the mContent
322 : * synchronously. The caller must ensure that it's safe to dispatch the event.
323 : */
324 : void DispatchCompositionEvent(WidgetCompositionEvent* aCompositionEvent,
325 : nsEventStatus* aStatus,
326 : EventDispatchingCallback* aCallBack,
327 : bool aIsSynthesized);
328 :
329 : /**
330 : * Simply calling EventDispatcher::Dispatch() with plugin event.
331 : * If dispatching event has no orginal clone, aOriginalEvent can be null.
332 : */
333 : void DispatchEvent(WidgetCompositionEvent* aDispatchEvent,
334 : nsEventStatus* aStatus,
335 : EventDispatchingCallback* aCallback,
336 : const WidgetCompositionEvent *aOriginalEvent = nullptr);
337 :
338 : /**
339 : * HandleSelectionEvent() sends the selection event to ContentEventHandler
340 : * or dispatches it to the focused child process.
341 : */
342 0 : void HandleSelectionEvent(WidgetSelectionEvent* aSelectionEvent)
343 : {
344 0 : HandleSelectionEvent(mPresContext, mTabParent, aSelectionEvent);
345 0 : }
346 : static void HandleSelectionEvent(nsPresContext* aPresContext,
347 : TabParent* aTabParent,
348 : WidgetSelectionEvent* aSelectionEvent);
349 :
350 : /**
351 : * MaybeDispatchCompositionUpdate() may dispatch a compositionupdate event
352 : * if aCompositionEvent changes composition string.
353 : * @return Returns false if dispatching the compositionupdate event caused
354 : * destroying this composition.
355 : */
356 : bool MaybeDispatchCompositionUpdate(
357 : const WidgetCompositionEvent* aCompositionEvent);
358 :
359 : /**
360 : * CloneAndDispatchAs() dispatches a composition event which is
361 : * duplicateed from aCompositionEvent and set the aMessage.
362 : *
363 : * @return Returns BaseEventFlags which is the result of dispatched event.
364 : */
365 : BaseEventFlags CloneAndDispatchAs(
366 : const WidgetCompositionEvent* aCompositionEvent,
367 : EventMessage aMessage,
368 : nsEventStatus* aStatus = nullptr,
369 : EventDispatchingCallback* aCallBack = nullptr);
370 :
371 : /**
372 : * If IME has already dispatched compositionend event but it was discarded
373 : * by PresShell due to not safe to dispatch, this returns true.
374 : */
375 0 : bool WasNativeCompositionEndEventDiscarded() const
376 : {
377 0 : return mWasNativeCompositionEndEventDiscarded;
378 : }
379 :
380 : /**
381 : * OnCompositionEventDiscarded() is called when PresShell discards
382 : * compositionupdate, compositionend or compositionchange event due to not
383 : * safe to dispatch event.
384 : */
385 : void OnCompositionEventDiscarded(WidgetCompositionEvent* aCompositionEvent);
386 :
387 : /**
388 : * OnCompositionEventDispatched() is called after a composition event is
389 : * dispatched.
390 : */
391 : void OnCompositionEventDispatched(
392 : const WidgetCompositionEvent* aDispatchEvent);
393 :
394 : /**
395 : * MaybeNotifyIMEOfCompositionEventHandled() notifies IME of composition
396 : * event handled. This should be called after dispatching a composition
397 : * event which came from widget.
398 : */
399 : void MaybeNotifyIMEOfCompositionEventHandled(
400 : const WidgetCompositionEvent* aCompositionEvent);
401 :
402 : /**
403 : * GetSelectionStartOffset() returns normal selection start offset in the
404 : * editor which has this composition.
405 : * If it failed or lost focus, this would return 0.
406 : */
407 : uint32_t GetSelectionStartOffset();
408 :
409 : /**
410 : * OnStartOffsetUpdatedInChild() is called when composition start offset
411 : * is updated in the child process. I.e., this is called and never called
412 : * if the composition is in this process.
413 : * @param aStartOffset New composition start offset with native
414 : * linebreaks.
415 : */
416 : void OnStartOffsetUpdatedInChild(uint32_t aStartOffset);
417 :
418 : /**
419 : * CompositionEventDispatcher dispatches the specified composition (or text)
420 : * event.
421 : */
422 0 : class CompositionEventDispatcher : public Runnable
423 : {
424 : public:
425 : CompositionEventDispatcher(TextComposition* aTextComposition,
426 : nsINode* aEventTarget,
427 : EventMessage aEventMessage,
428 : const nsAString& aData,
429 : bool aIsSynthesizedEvent = false);
430 : NS_IMETHOD Run() override;
431 :
432 : private:
433 : RefPtr<TextComposition> mTextComposition;
434 : nsCOMPtr<nsINode> mEventTarget;
435 : nsString mData;
436 : EventMessage mEventMessage;
437 : bool mIsSynthesizedEvent;
438 :
439 : CompositionEventDispatcher()
440 : : Runnable("TextComposition::CompositionEventDispatcher")
441 : , mIsSynthesizedEvent(false){};
442 : };
443 :
444 : /**
445 : * DispatchCompositionEventRunnable() dispatches a composition event to the
446 : * content. Be aware, if you use this method, nsPresShellEventCB isn't used.
447 : * That means that nsIFrame::HandleEvent() is never called.
448 : * WARNING: The instance which is managed by IMEStateManager may be
449 : * destroyed by this method call.
450 : *
451 : * @param aEventMessage Must be one of composition events.
452 : * @param aData Used for mData value.
453 : * @param aIsSynthesizingCommit true if this is called for synthesizing
454 : * commit or cancel composition. Otherwise,
455 : * false.
456 : */
457 : void DispatchCompositionEventRunnable(EventMessage aEventMessage,
458 : const nsAString& aData,
459 : bool aIsSynthesizingCommit = false);
460 : };
461 :
462 : /**
463 : * TextCompositionArray manages the instances of TextComposition class.
464 : * Managing with array is enough because only one composition is typically
465 : * there. Even if user switches native IME context, it's very rare that
466 : * second or more composition is started.
467 : * It's assumed that this is used by IMEStateManager for storing all active
468 : * compositions in the process. If the instance is it, each TextComposition
469 : * in the array can be destroyed by calling some methods of itself.
470 : */
471 :
472 0 : class TextCompositionArray final :
473 : public AutoTArray<RefPtr<TextComposition>, 2>
474 : {
475 : public:
476 : // Looking for per native IME context.
477 : index_type IndexOf(const widget::NativeIMEContext& aNativeIMEContext);
478 : index_type IndexOf(nsIWidget* aWidget);
479 :
480 : TextComposition* GetCompositionFor(nsIWidget* aWidget);
481 : TextComposition* GetCompositionFor(
482 : const WidgetCompositionEvent* aCompositionEvent);
483 :
484 : // Looking for per nsPresContext
485 : index_type IndexOf(nsPresContext* aPresContext);
486 : index_type IndexOf(nsPresContext* aPresContext, nsINode* aNode);
487 :
488 : TextComposition* GetCompositionFor(nsPresContext* aPresContext);
489 : TextComposition* GetCompositionFor(nsPresContext* aPresContext,
490 : nsINode* aNode);
491 : TextComposition* GetCompositionInContent(nsPresContext* aPresContext,
492 : nsIContent* aContent);
493 : };
494 :
495 : } // namespace mozilla
496 :
497 : #endif // #ifndef mozilla_TextComposition_h
|