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