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