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_IMEStateManager_h_
8 #define mozilla_IMEStateManager_h_
9 
10 #include "mozilla/EventForwards.h"
11 #include "mozilla/Maybe.h"
12 #include "mozilla/StaticPtr.h"
13 #include "mozilla/dom/BrowserParent.h"
14 #include "nsIWidget.h"
15 
16 class nsIContent;
17 class nsINode;
18 class nsPresContext;
19 
20 namespace mozilla {
21 
22 class EditorBase;
23 class EventDispatchingCallback;
24 class IMEContentObserver;
25 class TextCompositionArray;
26 class TextComposition;
27 
28 namespace dom {
29 class Selection;
30 }  // namespace dom
31 
32 /**
33  * IMEStateManager manages InputContext (e.g., active editor type, IME enabled
34  * state and IME open state) of nsIWidget instances, manages IMEContentObserver
35  * and provides useful API for IME.
36  */
37 
38 class IMEStateManager {
39   typedef dom::BrowserParent BrowserParent;
40   typedef widget::IMEMessage IMEMessage;
41   typedef widget::IMENotification IMENotification;
42   typedef widget::IMEState IMEState;
43   typedef widget::InputContext InputContext;
44   typedef widget::InputContextAction InputContextAction;
45 
46  public:
47   static void Init();
48   static void Shutdown();
49 
50   /**
51    * GetActiveBrowserParent() returns a pointer to a BrowserParent instance
52    * which is managed by the focused content (sContent).  If the focused content
53    * isn't managing another process, this returns nullptr.
54    */
GetActiveBrowserParent()55   static BrowserParent* GetActiveBrowserParent() {
56     // If menu has pseudo focus, we should ignore active child process.
57     if (sInstalledMenuKeyboardListener) {
58       return nullptr;
59     }
60     // If we know focused browser parent, use it for making any events related
61     // to composition go to same content process.
62     if (sFocusedIMEBrowserParent) {
63       return sFocusedIMEBrowserParent;
64     }
65     return BrowserParent::GetFocused();
66   }
67 
68   /**
69    * DoesBrowserParentHaveIMEFocus() returns true when aBrowserParent has IME
70    * focus, i.e., the BrowserParent sent "focus" notification but not yet sends
71    * "blur". Note that this doesn't check if the remote processes are same
72    * because if another BrowserParent has focus, committing composition causes
73    * firing composition events in different BrowserParent.  (Anyway, such case
74    * shouldn't occur.)
75    */
DoesBrowserParentHaveIMEFocus(const BrowserParent * aBrowserParent)76   static bool DoesBrowserParentHaveIMEFocus(
77       const BrowserParent* aBrowserParent) {
78     MOZ_ASSERT(aBrowserParent);
79     return sFocusedIMEBrowserParent == aBrowserParent;
80   }
81 
82   /**
83    * If CanSendNotificationToWidget() returns false (it should occur
84    * only in a content process), we shouldn't notify the widget of
85    * any focused editor changes since the content process was blurred.
86    * Also, even if content process, widget has native text event dispatcher such
87    * as Android, it still notify it.
88    */
CanSendNotificationToWidget()89   static bool CanSendNotificationToWidget() {
90 #ifdef MOZ_WIDGET_ANDROID
91     return true;
92 #else
93     return !sCleaningUpForStoppingIMEStateManagement;
94 #endif
95   }
96 
97   /**
98    * Focus moved between browsers from aBlur to aFocus. (nullptr means the
99    * chrome process.)
100    */
101   static void OnFocusMovedBetweenBrowsers(BrowserParent* aBlur,
102                                           BrowserParent* aFocus);
103 
104   /**
105    * Called when aWidget is being deleted.
106    */
107   static void WidgetDestroyed(nsIWidget* aWidget);
108 
109   /**
110    * Called when a widget exists when the app is quitting
111    */
112   static void WidgetOnQuit(nsIWidget* aWidget);
113 
114   /**
115    * GetWidgetForActiveInputContext() returns a widget which IMEStateManager
116    * is managing input context with.  If a widget instance needs to cache
117    * the last input context for nsIWidget::GetInputContext() or something,
118    * it should check if its cache is valid with this method before using it
119    * because if this method returns another instance, it means that
120    * IMEStateManager may have already changed shared input context via the
121    * widget.
122    */
GetWidgetForActiveInputContext()123   static nsIWidget* GetWidgetForActiveInputContext() {
124     return sActiveInputContextWidget;
125   }
126 
127   /**
128    * SetIMEContextForChildProcess() is called when aBrowserParent receives
129    * SetInputContext() from the remote process.
130    */
131   static void SetInputContextForChildProcess(BrowserParent* aBrowserParent,
132                                              const InputContext& aInputContext,
133                                              const InputContextAction& aAction);
134 
135   /**
136    * StopIMEStateManagement() is called when the process should stop managing
137    * IME state.
138    */
139   static void StopIMEStateManagement();
140 
141   /**
142    * MaybeStartOffsetUpdatedInChild() is called when composition start offset
143    * is maybe updated in the child process.  I.e., even if it's not updated,
144    * this is called and never called if the composition is in this process.
145    * @param aWidget             The widget whose native IME context has the
146    *                            composition.
147    * @param aStartOffset        New composition start offset with native
148    *                            linebreaks.
149    */
150   static void MaybeStartOffsetUpdatedInChild(nsIWidget* aWidget,
151                                              uint32_t aStartOffset);
152 
153   static nsresult OnDestroyPresContext(nsPresContext* aPresContext);
154   static nsresult OnRemoveContent(nsPresContext* aPresContext,
155                                   nsIContent* aContent);
156   /**
157    * OnChangeFocus() should be called when focused content is changed or
158    * IME enabled state is changed.  If nobody has focus, set both aPresContext
159    * and aContent nullptr.  E.g., all windows are deactivated.
160    */
161   static nsresult OnChangeFocus(nsPresContext* aPresContext,
162                                 nsIContent* aContent,
163                                 InputContextAction::Cause aCause);
164 
165   /**
166    * OnInstalledMenuKeyboardListener() is called when menu keyboard listener
167    * is installed or uninstalled in the process.  So, even if menu keyboard
168    * listener was installed in chrome process, this won't be called in content
169    * processes.
170    *
171    * @param aInstalling     true if menu keyboard listener is installed.
172    *                        Otherwise, i.e., menu keyboard listener is
173    *                        uninstalled, false.
174    */
175   static void OnInstalledMenuKeyboardListener(bool aInstalling);
176 
177   // These two methods manage focus and selection/text observers.
178   // They are separate from OnChangeFocus above because this offers finer
179   // control compared to having the two methods incorporated into OnChangeFocus
180 
181   // Get the focused editor's selection and root
182   static nsresult GetFocusSelectionAndRoot(dom::Selection** aSel,
183                                            nsIContent** aRoot);
184   // This method updates the current IME state.  However, if the enabled state
185   // isn't changed by the new state, this method does nothing.
186   // Note that this method changes the IME state of the active element in the
187   // widget.  So, the caller must have focus.
188   // XXX Changing this to MOZ_CAN_RUN_SCRIPT requires too many callers to be
189   //     marked too.  Probably, we should initialize IMEContentObserver
190   //     asynchronously.
191   MOZ_CAN_RUN_SCRIPT_BOUNDARY static void UpdateIMEState(
192       const IMEState& aNewIMEState, nsIContent* aContent,
193       EditorBase& aEditorBase);
194 
195   // This method is called when user operates mouse button in focused editor
196   // and before the editor handles it.
197   // Returns true if IME consumes the event.  Otherwise, false.
198   MOZ_CAN_RUN_SCRIPT static bool OnMouseButtonEventInEditor(
199       nsPresContext* aPresContext, nsIContent* aContent,
200       WidgetMouseEvent* aMouseEvent);
201 
202   // This method is called when user clicked in an editor.
203   // aContent must be:
204   //   If the editor is for <input> or <textarea>, the element.
205   //   If the editor is for contenteditable, the active editinghost.
206   //   If the editor is for designMode, nullptr.
207   static void OnClickInEditor(nsPresContext* aPresContext, nsIContent* aContent,
208                               const WidgetMouseEvent* aMouseEvent);
209 
210   // This method is called when editor actually gets focus.
211   // aContent must be:
212   //   If the editor is for <input> or <textarea>, the element.
213   //   If the editor is for contenteditable, the active editinghost.
214   //   If the editor is for designMode, nullptr.
215   static void OnFocusInEditor(nsPresContext* aPresContext, nsIContent* aContent,
216                               EditorBase& aEditorBase);
217 
218   // This method is called when the editor is initialized.
219   static void OnEditorInitialized(EditorBase& aEditorBase);
220 
221   // This method is called when the editor is (might be temporarily) being
222   // destroyed.
223   static void OnEditorDestroying(EditorBase& aEditorBase);
224 
225   // This method is called when focus is set to same content again.
226   static void OnReFocus(nsPresContext* aPresContext, nsIContent& aContent);
227 
228   /**
229    * All composition events must be dispatched via DispatchCompositionEvent()
230    * for storing the composition target and ensuring a set of composition
231    * events must be fired the stored target.  If the stored composition event
232    * target is destroying, this removes the stored composition automatically.
233    */
234   MOZ_CAN_RUN_SCRIPT static void DispatchCompositionEvent(
235       nsINode* aEventTargetNode, nsPresContext* aPresContext,
236       BrowserParent* aBrowserParent, WidgetCompositionEvent* aCompositionEvent,
237       nsEventStatus* aStatus, EventDispatchingCallback* aCallBack,
238       bool aIsSynthesized = false);
239 
240   /**
241    * All selection events must be handled via HandleSelectionEvent()
242    * because they must be handled by same target as composition events when
243    * there is a composition.
244    */
245   MOZ_CAN_RUN_SCRIPT
246   static void HandleSelectionEvent(nsPresContext* aPresContext,
247                                    nsIContent* aEventTargetContent,
248                                    WidgetSelectionEvent* aSelectionEvent);
249 
250   /**
251    * This is called when PresShell ignores a composition event due to not safe
252    * to dispatch events.
253    */
254   static void OnCompositionEventDiscarded(
255       WidgetCompositionEvent* aCompositionEvent);
256 
257   /**
258    * Get TextComposition from widget.
259    */
260   static already_AddRefed<TextComposition> GetTextCompositionFor(
261       nsIWidget* aWidget);
262 
263   /**
264    * Returns TextComposition instance for the event.
265    */
266   static already_AddRefed<TextComposition> GetTextCompositionFor(
267       const WidgetCompositionEvent* aCompositionEvent);
268 
269   /**
270    * Returns TextComposition instance for the pres context.
271    * Be aware, even if another pres context which shares native IME context with
272    * specified pres context has composition, this returns nullptr.
273    */
274   static already_AddRefed<TextComposition> GetTextCompositionFor(
275       nsPresContext* aPresContext);
276 
277   /**
278    * Send a notification to IME.  It depends on the IME or platform spec what
279    * will occur (or not occur).
280    */
281   static nsresult NotifyIME(const IMENotification& aNotification,
282                             nsIWidget* aWidget,
283                             BrowserParent* aBrowserParent = nullptr);
284   static nsresult NotifyIME(IMEMessage aMessage, nsIWidget* aWidget,
285                             BrowserParent* aBrowserParent = nullptr);
286   static nsresult NotifyIME(IMEMessage aMessage, nsPresContext* aPresContext,
287                             BrowserParent* aBrowserParent = nullptr);
288 
289   static nsINode* GetRootEditableNode(nsPresContext* aPresContext,
290                                       nsIContent* aContent);
291 
292   /**
293    * Returns active IMEContentObserver but may be nullptr if focused content
294    * isn't editable or focus in a remote process.
295    */
296   static IMEContentObserver* GetActiveContentObserver();
297 
298  protected:
299   static nsresult OnChangeFocusInternal(nsPresContext* aPresContext,
300                                         nsIContent* aContent,
301                                         InputContextAction aAction);
302   static void SetIMEState(const IMEState& aState, nsPresContext* aPresContext,
303                           nsIContent* aContent, nsIWidget* aWidget,
304                           InputContextAction aAction,
305                           InputContext::Origin aOrigin);
306   static void SetInputContext(nsIWidget* aWidget,
307                               const InputContext& aInputContext,
308                               const InputContextAction& aAction);
309   static IMEState GetNewIMEState(nsPresContext* aPresContext,
310                                  nsIContent* aContent);
311 
312   static void EnsureTextCompositionArray();
313 
314   // XXX Changing this to MOZ_CAN_RUN_SCRIPT requires too many callers to be
315   //     marked too.  Probably, we should initialize IMEContentObserver
316   //     asynchronously.
317   MOZ_CAN_RUN_SCRIPT_BOUNDARY static void CreateIMEContentObserver(
318       EditorBase& aEditorBase);
319 
320   static void DestroyIMEContentObserver();
321 
322   static bool IsEditable(nsINode* node);
323 
324   static bool IsIMEObserverNeeded(const IMEState& aState);
325 
326   static nsIContent* GetRootContent(nsPresContext* aPresContext);
327 
328   /**
329    * CanHandleWith() returns false if aPresContext is nullptr or it's destroyed.
330    */
331   static bool CanHandleWith(nsPresContext* aPresContext);
332 
333   /**
334    * ResetActiveChildInputContext() resets sActiveChildInputContext.
335    * So, HasActiveChildSetInputContext() will return false until a remote
336    * process gets focus and set input context.
337    */
338   static void ResetActiveChildInputContext();
339 
340   /**
341    * HasActiveChildSetInputContext() returns true if a remote tab has focus
342    * and it has already set input context.  Otherwise, returns false.
343    */
344   static bool HasActiveChildSetInputContext();
345 
346   // sContent and sPresContext are the focused content and PresContext.  If a
347   // document has focus but there is no focused element, sContent may be
348   // nullptr.
349   static StaticRefPtr<nsIContent> sContent;
350   static StaticRefPtr<nsPresContext> sPresContext;
351   // sWidget is cache for the root widget of sPresContext.  Even afer
352   // sPresContext has gone, we need to clean up some IME state on the widget
353   // if the widget is available.
354   static nsIWidget* sWidget;
355   // sFocusedIMEBrowserParent is the tab parent, which send "focus" notification
356   // to sFocusedIMEWidget (and didn't yet sent "blur" notification).
357   static nsIWidget* sFocusedIMEWidget;
358   static StaticRefPtr<BrowserParent> sFocusedIMEBrowserParent;
359   // sActiveInputContextWidget is the last widget whose SetInputContext() is
360   // called.  This is important to reduce sync IPC cost with parent process.
361   // If IMEStateManager set input context to different widget, PuppetWidget can
362   // return cached input context safely.
363   static nsIWidget* sActiveInputContextWidget;
364   // sActiveIMEContentObserver points to the currently active
365   // IMEContentObserver.  This is null if there is no focused editor.
366   static StaticRefPtr<IMEContentObserver> sActiveIMEContentObserver;
367 
368   // All active compositions in the process are stored by this array.
369   // When you get an item of this array and use it, please be careful.
370   // The instances in this array can be destroyed automatically if you do
371   // something to cause committing or canceling the composition.
372   static TextCompositionArray* sTextCompositions;
373 
374   // Origin type of current process.
375   static InputContext::Origin sOrigin;
376 
377   // sActiveChildInputContext is valid only when BrowserParent::GetFocused() is
378   // not nullptr.  This stores last information of input context in the remote
379   // process of BrowserParent::GetFocused().  I.e., they are set when
380   // SetInputContextForChildProcess() is called.  This is necessary for
381   // restoring IME state when menu keyboard listener is uninstalled.
382   static InputContext sActiveChildInputContext;
383 
384   // sInstalledMenuKeyboardListener is true if menu keyboard listener is
385   // installed in the process.
386   static bool sInstalledMenuKeyboardListener;
387 
388   static bool sIsGettingNewIMEState;
389   static bool sCheckForIMEUnawareWebApps;
390 
391   // Set to true only if this is an instance in a content process and
392   // only while `IMEStateManager::StopIMEStateManagement()`.
393   static bool sCleaningUpForStoppingIMEStateManagement;
394 
395   // Set to true when:
396   // - In the main process, a window belonging to this app is active in the
397   //   desktop.
398   // - In a content process, the process has focus.
399   //
400   // This is updated by `OnChangeFocusInternal()` is called in the main
401   // process.  Therefore, this indicates the active state which
402   // `IMEStateManager` notified the focus change, there is timelag from
403   // the `nsFocusManager`'s status update.  This allows that all methods
404   // to handle something specially when they are called while the process
405   // is being activated or inactivated.  E.g., `OnFocusMovedBetweenBrowsers()`
406   // is called twice before `OnChangeFocusInternal()` when the main process
407   // becomes active.  In this case, it wants to wait a following call of
408   // `OnChangeFocusInternal()` to keep active composition.  See also below.
409   static bool sIsActive;
410 
411   // While the application is being activated, `OnFocusMovedBetweenBrowsers()`
412   // are called twice before `OnChangeFocusInternal()`.  First time, aBlur is
413   // the last focused `BrowserParent` at deactivating and aFocus is always
414   // `nullptr`.  Then, it'll be called again with actually focused
415   // `BrowserParent` when a content in a remote process has focus.  If we need
416   // to keep active composition while all windows are deactivated, we shouldn't
417   // commit it at the first call since usually, the second call's aFocus
418   // and the first call's aBlur are same `BrowserParent`.  For solving this
419   // issue, we need to merge the given `BrowserParent`s of multiple calls of
420   // `OnFocusMovedBetweenBrowsers()`. The following struct is the data for
421   // calling `OnFocusMovedBetweenBrowsers()` later from
422   // `OnChangeFocusInternal()`.  Note that focus can be moved even while the
423   // main process is not active because JS can change focus.  In such case,
424   // composition is committed at that time.  Therefore, this is required only
425   // when the main process is activated and there is a composition in a remote
426   // process.
427   struct PendingFocusedBrowserSwitchingData final {
428     RefPtr<BrowserParent> mBrowserParentBlurred;
429     RefPtr<BrowserParent> mBrowserParentFocused;
430 
431     PendingFocusedBrowserSwitchingData() = delete;
PendingFocusedBrowserSwitchingDatafinal432     explicit PendingFocusedBrowserSwitchingData(BrowserParent* aBlur,
433                                                 BrowserParent* aFocus)
434         : mBrowserParentBlurred(aBlur), mBrowserParentFocused(aFocus) {}
435   };
436   static Maybe<PendingFocusedBrowserSwitchingData>
437       sPendingFocusedBrowserSwitchingData;
438 
439   class MOZ_STACK_CLASS GettingNewIMEStateBlocker final {
440    public:
GettingNewIMEStateBlocker()441     GettingNewIMEStateBlocker()
442         : mOldValue(IMEStateManager::sIsGettingNewIMEState) {
443       IMEStateManager::sIsGettingNewIMEState = true;
444     }
~GettingNewIMEStateBlocker()445     ~GettingNewIMEStateBlocker() {
446       IMEStateManager::sIsGettingNewIMEState = mOldValue;
447     }
448 
449    private:
450     bool mOldValue;
451   };
452 };
453 
454 }  // namespace mozilla
455 
456 #endif  // mozilla_IMEStateManager_h_
457