dom/events/IMEStateManager.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=2 sw=2 et tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "mozilla/IMEStateManager.h"
michael@0 8
michael@0 9 #include "mozilla/Attributes.h"
michael@0 10 #include "mozilla/EventStates.h"
michael@0 11 #include "mozilla/Preferences.h"
michael@0 12 #include "mozilla/Services.h"
michael@0 13 #include "mozilla/TextComposition.h"
michael@0 14 #include "mozilla/TextEvents.h"
michael@0 15 #include "mozilla/dom/HTMLFormElement.h"
michael@0 16
michael@0 17 #include "HTMLInputElement.h"
michael@0 18 #include "IMEContentObserver.h"
michael@0 19
michael@0 20 #include "nsCOMPtr.h"
michael@0 21 #include "nsContentUtils.h"
michael@0 22 #include "nsIContent.h"
michael@0 23 #include "nsIDocument.h"
michael@0 24 #include "nsIDOMMouseEvent.h"
michael@0 25 #include "nsIForm.h"
michael@0 26 #include "nsIFormControl.h"
michael@0 27 #include "nsINode.h"
michael@0 28 #include "nsIObserverService.h"
michael@0 29 #include "nsIPresShell.h"
michael@0 30 #include "nsISelection.h"
michael@0 31 #include "nsISupports.h"
michael@0 32 #include "nsPresContext.h"
michael@0 33
michael@0 34 namespace mozilla {
michael@0 35
michael@0 36 using namespace dom;
michael@0 37 using namespace widget;
michael@0 38
michael@0 39 nsIContent* IMEStateManager::sContent = nullptr;
michael@0 40 nsPresContext* IMEStateManager::sPresContext = nullptr;
michael@0 41 bool IMEStateManager::sInstalledMenuKeyboardListener = false;
michael@0 42 bool IMEStateManager::sIsTestingIME = false;
michael@0 43
michael@0 44 // sActiveIMEContentObserver points to the currently active IMEContentObserver.
michael@0 45 // sActiveIMEContentObserver is null if there is no focused editor.
michael@0 46 IMEContentObserver* IMEStateManager::sActiveIMEContentObserver = nullptr;
michael@0 47 TextCompositionArray* IMEStateManager::sTextCompositions = nullptr;
michael@0 48
michael@0 49 void
michael@0 50 IMEStateManager::Shutdown()
michael@0 51 {
michael@0 52 MOZ_ASSERT(!sTextCompositions || !sTextCompositions->Length());
michael@0 53 delete sTextCompositions;
michael@0 54 sTextCompositions = nullptr;
michael@0 55 }
michael@0 56
michael@0 57 nsresult
michael@0 58 IMEStateManager::OnDestroyPresContext(nsPresContext* aPresContext)
michael@0 59 {
michael@0 60 NS_ENSURE_ARG_POINTER(aPresContext);
michael@0 61
michael@0 62 // First, if there is a composition in the aPresContext, clean up it.
michael@0 63 if (sTextCompositions) {
michael@0 64 TextCompositionArray::index_type i =
michael@0 65 sTextCompositions->IndexOf(aPresContext);
michael@0 66 if (i != TextCompositionArray::NoIndex) {
michael@0 67 // there should be only one composition per presContext object.
michael@0 68 sTextCompositions->ElementAt(i)->Destroy();
michael@0 69 sTextCompositions->RemoveElementAt(i);
michael@0 70 MOZ_ASSERT(sTextCompositions->IndexOf(aPresContext) ==
michael@0 71 TextCompositionArray::NoIndex);
michael@0 72 }
michael@0 73 }
michael@0 74
michael@0 75 if (aPresContext != sPresContext) {
michael@0 76 return NS_OK;
michael@0 77 }
michael@0 78
michael@0 79 DestroyTextStateManager();
michael@0 80
michael@0 81 nsCOMPtr<nsIWidget> widget = sPresContext->GetRootWidget();
michael@0 82 if (widget) {
michael@0 83 IMEState newState = GetNewIMEState(sPresContext, nullptr);
michael@0 84 InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
michael@0 85 InputContextAction::LOST_FOCUS);
michael@0 86 SetIMEState(newState, nullptr, widget, action);
michael@0 87 }
michael@0 88 NS_IF_RELEASE(sContent);
michael@0 89 sPresContext = nullptr;
michael@0 90 return NS_OK;
michael@0 91 }
michael@0 92
michael@0 93 nsresult
michael@0 94 IMEStateManager::OnRemoveContent(nsPresContext* aPresContext,
michael@0 95 nsIContent* aContent)
michael@0 96 {
michael@0 97 NS_ENSURE_ARG_POINTER(aPresContext);
michael@0 98
michael@0 99 // First, if there is a composition in the aContent, clean up it.
michael@0 100 if (sTextCompositions) {
michael@0 101 nsRefPtr<TextComposition> compositionInContent =
michael@0 102 sTextCompositions->GetCompositionInContent(aPresContext, aContent);
michael@0 103
michael@0 104 if (compositionInContent) {
michael@0 105 // Try resetting the native IME state. Be aware, typically, this method
michael@0 106 // is called during the content being removed. Then, the native
michael@0 107 // composition events which are caused by following APIs are ignored due
michael@0 108 // to unsafe to run script (in PresShell::HandleEvent()).
michael@0 109 nsCOMPtr<nsIWidget> widget = aPresContext->GetRootWidget();
michael@0 110 if (widget) {
michael@0 111 nsresult rv =
michael@0 112 compositionInContent->NotifyIME(REQUEST_TO_CANCEL_COMPOSITION);
michael@0 113 if (NS_FAILED(rv)) {
michael@0 114 compositionInContent->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
michael@0 115 }
michael@0 116 // By calling the APIs, the composition may have been finished normally.
michael@0 117 compositionInContent =
michael@0 118 sTextCompositions->GetCompositionFor(
michael@0 119 compositionInContent->GetPresContext(),
michael@0 120 compositionInContent->GetEventTargetNode());
michael@0 121 }
michael@0 122 }
michael@0 123
michael@0 124 // If the compositionInContent is still available, we should finish the
michael@0 125 // composition just on the content forcibly.
michael@0 126 if (compositionInContent) {
michael@0 127 compositionInContent->SynthesizeCommit(true);
michael@0 128 }
michael@0 129 }
michael@0 130
michael@0 131 if (!sPresContext || !sContent ||
michael@0 132 !nsContentUtils::ContentIsDescendantOf(sContent, aContent)) {
michael@0 133 return NS_OK;
michael@0 134 }
michael@0 135
michael@0 136 DestroyTextStateManager();
michael@0 137
michael@0 138 // Current IME transaction should commit
michael@0 139 nsCOMPtr<nsIWidget> widget = sPresContext->GetRootWidget();
michael@0 140 if (widget) {
michael@0 141 IMEState newState = GetNewIMEState(sPresContext, nullptr);
michael@0 142 InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
michael@0 143 InputContextAction::LOST_FOCUS);
michael@0 144 SetIMEState(newState, nullptr, widget, action);
michael@0 145 }
michael@0 146
michael@0 147 NS_IF_RELEASE(sContent);
michael@0 148 sPresContext = nullptr;
michael@0 149
michael@0 150 return NS_OK;
michael@0 151 }
michael@0 152
michael@0 153 nsresult
michael@0 154 IMEStateManager::OnChangeFocus(nsPresContext* aPresContext,
michael@0 155 nsIContent* aContent,
michael@0 156 InputContextAction::Cause aCause)
michael@0 157 {
michael@0 158 InputContextAction action(aCause);
michael@0 159 return OnChangeFocusInternal(aPresContext, aContent, action);
michael@0 160 }
michael@0 161
michael@0 162 nsresult
michael@0 163 IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext,
michael@0 164 nsIContent* aContent,
michael@0 165 InputContextAction aAction)
michael@0 166 {
michael@0 167 bool focusActuallyChanging =
michael@0 168 (sContent != aContent || sPresContext != aPresContext);
michael@0 169
michael@0 170 nsCOMPtr<nsIWidget> oldWidget =
michael@0 171 sPresContext ? sPresContext->GetRootWidget() : nullptr;
michael@0 172 if (oldWidget && focusActuallyChanging) {
michael@0 173 // If we're deactivating, we shouldn't commit composition forcibly because
michael@0 174 // the user may want to continue the composition.
michael@0 175 if (aPresContext) {
michael@0 176 NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, oldWidget);
michael@0 177 }
michael@0 178 }
michael@0 179
michael@0 180 if (sActiveIMEContentObserver &&
michael@0 181 (aPresContext || !sActiveIMEContentObserver->KeepAliveDuringDeactive()) &&
michael@0 182 !sActiveIMEContentObserver->IsManaging(aPresContext, aContent)) {
michael@0 183 DestroyTextStateManager();
michael@0 184 }
michael@0 185
michael@0 186 if (!aPresContext) {
michael@0 187 return NS_OK;
michael@0 188 }
michael@0 189
michael@0 190 nsCOMPtr<nsIWidget> widget =
michael@0 191 (sPresContext == aPresContext) ? oldWidget.get() :
michael@0 192 aPresContext->GetRootWidget();
michael@0 193 if (!widget) {
michael@0 194 return NS_OK;
michael@0 195 }
michael@0 196
michael@0 197 IMEState newState = GetNewIMEState(aPresContext, aContent);
michael@0 198 if (!focusActuallyChanging) {
michael@0 199 // actual focus isn't changing, but if IME enabled state is changing,
michael@0 200 // we should do it.
michael@0 201 InputContext context = widget->GetInputContext();
michael@0 202 if (context.mIMEState.mEnabled == newState.mEnabled) {
michael@0 203 // the enabled state isn't changing.
michael@0 204 return NS_OK;
michael@0 205 }
michael@0 206 aAction.mFocusChange = InputContextAction::FOCUS_NOT_CHANGED;
michael@0 207
michael@0 208 // Even if focus isn't changing actually, we should commit current
michael@0 209 // composition here since the IME state is changing.
michael@0 210 if (sPresContext && oldWidget && !focusActuallyChanging) {
michael@0 211 NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, oldWidget);
michael@0 212 }
michael@0 213 } else if (aAction.mFocusChange == InputContextAction::FOCUS_NOT_CHANGED) {
michael@0 214 // If aContent isn't null or aContent is null but editable, somebody gets
michael@0 215 // focus.
michael@0 216 bool gotFocus = aContent || (newState.mEnabled == IMEState::ENABLED);
michael@0 217 aAction.mFocusChange =
michael@0 218 gotFocus ? InputContextAction::GOT_FOCUS : InputContextAction::LOST_FOCUS;
michael@0 219 }
michael@0 220
michael@0 221 // Update IME state for new focus widget
michael@0 222 SetIMEState(newState, aContent, widget, aAction);
michael@0 223
michael@0 224 sPresContext = aPresContext;
michael@0 225 if (sContent != aContent) {
michael@0 226 NS_IF_RELEASE(sContent);
michael@0 227 NS_IF_ADDREF(sContent = aContent);
michael@0 228 }
michael@0 229
michael@0 230 // Don't call CreateIMEContentObserver() here, it should be called from
michael@0 231 // focus event handler of editor.
michael@0 232
michael@0 233 return NS_OK;
michael@0 234 }
michael@0 235
michael@0 236 void
michael@0 237 IMEStateManager::OnInstalledMenuKeyboardListener(bool aInstalling)
michael@0 238 {
michael@0 239 sInstalledMenuKeyboardListener = aInstalling;
michael@0 240
michael@0 241 InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
michael@0 242 aInstalling ? InputContextAction::MENU_GOT_PSEUDO_FOCUS :
michael@0 243 InputContextAction::MENU_LOST_PSEUDO_FOCUS);
michael@0 244 OnChangeFocusInternal(sPresContext, sContent, action);
michael@0 245 }
michael@0 246
michael@0 247 void
michael@0 248 IMEStateManager::OnClickInEditor(nsPresContext* aPresContext,
michael@0 249 nsIContent* aContent,
michael@0 250 nsIDOMMouseEvent* aMouseEvent)
michael@0 251 {
michael@0 252 if (sPresContext != aPresContext || sContent != aContent) {
michael@0 253 return;
michael@0 254 }
michael@0 255
michael@0 256 nsCOMPtr<nsIWidget> widget = aPresContext->GetRootWidget();
michael@0 257 NS_ENSURE_TRUE_VOID(widget);
michael@0 258
michael@0 259 bool isTrusted;
michael@0 260 nsresult rv = aMouseEvent->GetIsTrusted(&isTrusted);
michael@0 261 NS_ENSURE_SUCCESS_VOID(rv);
michael@0 262 if (!isTrusted) {
michael@0 263 return; // ignore untrusted event.
michael@0 264 }
michael@0 265
michael@0 266 int16_t button;
michael@0 267 rv = aMouseEvent->GetButton(&button);
michael@0 268 NS_ENSURE_SUCCESS_VOID(rv);
michael@0 269 if (button != 0) {
michael@0 270 return; // not a left click event.
michael@0 271 }
michael@0 272
michael@0 273 int32_t clickCount;
michael@0 274 rv = aMouseEvent->GetDetail(&clickCount);
michael@0 275 NS_ENSURE_SUCCESS_VOID(rv);
michael@0 276 if (clickCount != 1) {
michael@0 277 return; // should notify only first click event.
michael@0 278 }
michael@0 279
michael@0 280 InputContextAction action(InputContextAction::CAUSE_MOUSE,
michael@0 281 InputContextAction::FOCUS_NOT_CHANGED);
michael@0 282 IMEState newState = GetNewIMEState(aPresContext, aContent);
michael@0 283 SetIMEState(newState, aContent, widget, action);
michael@0 284 }
michael@0 285
michael@0 286 void
michael@0 287 IMEStateManager::OnFocusInEditor(nsPresContext* aPresContext,
michael@0 288 nsIContent* aContent)
michael@0 289 {
michael@0 290 if (sPresContext != aPresContext || sContent != aContent) {
michael@0 291 return;
michael@0 292 }
michael@0 293
michael@0 294 // If the IMEContentObserver instance isn't managing the editor actually,
michael@0 295 // we need to recreate the instance.
michael@0 296 if (sActiveIMEContentObserver) {
michael@0 297 if (sActiveIMEContentObserver->IsManaging(aPresContext, aContent)) {
michael@0 298 return;
michael@0 299 }
michael@0 300 DestroyTextStateManager();
michael@0 301 }
michael@0 302
michael@0 303 CreateIMEContentObserver();
michael@0 304 }
michael@0 305
michael@0 306 void
michael@0 307 IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState,
michael@0 308 nsIContent* aContent)
michael@0 309 {
michael@0 310 if (!sPresContext) {
michael@0 311 NS_WARNING("ISM doesn't know which editor has focus");
michael@0 312 return;
michael@0 313 }
michael@0 314 nsCOMPtr<nsIWidget> widget = sPresContext->GetRootWidget();
michael@0 315 if (!widget) {
michael@0 316 NS_WARNING("focused widget is not found");
michael@0 317 return;
michael@0 318 }
michael@0 319
michael@0 320 // If the IMEContentObserver instance isn't managing the editor's current
michael@0 321 // editable root content, the editor frame might be reframed. We should
michael@0 322 // recreate the instance at that time.
michael@0 323 bool createTextStateManager =
michael@0 324 (!sActiveIMEContentObserver ||
michael@0 325 !sActiveIMEContentObserver->IsManaging(sPresContext, aContent));
michael@0 326
michael@0 327 bool updateIMEState =
michael@0 328 (widget->GetInputContext().mIMEState.mEnabled != aNewIMEState.mEnabled);
michael@0 329
michael@0 330 if (updateIMEState) {
michael@0 331 // commit current composition before modifying IME state.
michael@0 332 NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, widget);
michael@0 333 }
michael@0 334
michael@0 335 if (createTextStateManager) {
michael@0 336 DestroyTextStateManager();
michael@0 337 }
michael@0 338
michael@0 339 if (updateIMEState) {
michael@0 340 InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
michael@0 341 InputContextAction::FOCUS_NOT_CHANGED);
michael@0 342 SetIMEState(aNewIMEState, aContent, widget, action);
michael@0 343 }
michael@0 344
michael@0 345 if (createTextStateManager) {
michael@0 346 CreateIMEContentObserver();
michael@0 347 }
michael@0 348 }
michael@0 349
michael@0 350 IMEState
michael@0 351 IMEStateManager::GetNewIMEState(nsPresContext* aPresContext,
michael@0 352 nsIContent* aContent)
michael@0 353 {
michael@0 354 // On Printing or Print Preview, we don't need IME.
michael@0 355 if (aPresContext->Type() == nsPresContext::eContext_PrintPreview ||
michael@0 356 aPresContext->Type() == nsPresContext::eContext_Print) {
michael@0 357 return IMEState(IMEState::DISABLED);
michael@0 358 }
michael@0 359
michael@0 360 if (sInstalledMenuKeyboardListener) {
michael@0 361 return IMEState(IMEState::DISABLED);
michael@0 362 }
michael@0 363
michael@0 364 if (!aContent) {
michael@0 365 // Even if there are no focused content, the focused document might be
michael@0 366 // editable, such case is design mode.
michael@0 367 nsIDocument* doc = aPresContext->Document();
michael@0 368 if (doc && doc->HasFlag(NODE_IS_EDITABLE)) {
michael@0 369 return IMEState(IMEState::ENABLED);
michael@0 370 }
michael@0 371 return IMEState(IMEState::DISABLED);
michael@0 372 }
michael@0 373
michael@0 374 return aContent->GetDesiredIMEState();
michael@0 375 }
michael@0 376
michael@0 377 // Helper class, used for IME enabled state change notification
michael@0 378 class IMEEnabledStateChangedEvent : public nsRunnable {
michael@0 379 public:
michael@0 380 IMEEnabledStateChangedEvent(uint32_t aState)
michael@0 381 : mState(aState)
michael@0 382 {
michael@0 383 }
michael@0 384
michael@0 385 NS_IMETHOD Run()
michael@0 386 {
michael@0 387 nsCOMPtr<nsIObserverService> observerService =
michael@0 388 services::GetObserverService();
michael@0 389 if (observerService) {
michael@0 390 nsAutoString state;
michael@0 391 state.AppendInt(mState);
michael@0 392 observerService->NotifyObservers(nullptr, "ime-enabled-state-changed",
michael@0 393 state.get());
michael@0 394 }
michael@0 395 return NS_OK;
michael@0 396 }
michael@0 397
michael@0 398 private:
michael@0 399 uint32_t mState;
michael@0 400 };
michael@0 401
michael@0 402 void
michael@0 403 IMEStateManager::SetIMEState(const IMEState& aState,
michael@0 404 nsIContent* aContent,
michael@0 405 nsIWidget* aWidget,
michael@0 406 InputContextAction aAction)
michael@0 407 {
michael@0 408 NS_ENSURE_TRUE_VOID(aWidget);
michael@0 409
michael@0 410 InputContext oldContext = aWidget->GetInputContext();
michael@0 411
michael@0 412 InputContext context;
michael@0 413 context.mIMEState = aState;
michael@0 414
michael@0 415 if (aContent && aContent->GetNameSpaceID() == kNameSpaceID_XHTML &&
michael@0 416 (aContent->Tag() == nsGkAtoms::input ||
michael@0 417 aContent->Tag() == nsGkAtoms::textarea)) {
michael@0 418 if (aContent->Tag() != nsGkAtoms::textarea) {
michael@0 419 // <input type=number> has an anonymous <input type=text> descendant
michael@0 420 // that gets focus whenever anyone tries to focus the number control. We
michael@0 421 // need to check if aContent is one of those anonymous text controls and,
michael@0 422 // if so, use the number control instead:
michael@0 423 nsIContent* content = aContent;
michael@0 424 HTMLInputElement* inputElement =
michael@0 425 HTMLInputElement::FromContentOrNull(aContent);
michael@0 426 if (inputElement) {
michael@0 427 HTMLInputElement* ownerNumberControl =
michael@0 428 inputElement->GetOwnerNumberControl();
michael@0 429 if (ownerNumberControl) {
michael@0 430 content = ownerNumberControl; // an <input type=number>
michael@0 431 }
michael@0 432 }
michael@0 433 content->GetAttr(kNameSpaceID_None, nsGkAtoms::type,
michael@0 434 context.mHTMLInputType);
michael@0 435 } else {
michael@0 436 context.mHTMLInputType.Assign(nsGkAtoms::textarea->GetUTF16String());
michael@0 437 }
michael@0 438
michael@0 439 if (Preferences::GetBool("dom.forms.inputmode", false)) {
michael@0 440 aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::inputmode,
michael@0 441 context.mHTMLInputInputmode);
michael@0 442 }
michael@0 443
michael@0 444 aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::moz_action_hint,
michael@0 445 context.mActionHint);
michael@0 446
michael@0 447 // if we don't have an action hint and return won't submit the form use "next"
michael@0 448 if (context.mActionHint.IsEmpty() && aContent->Tag() == nsGkAtoms::input) {
michael@0 449 bool willSubmit = false;
michael@0 450 nsCOMPtr<nsIFormControl> control(do_QueryInterface(aContent));
michael@0 451 mozilla::dom::Element* formElement = control->GetFormElement();
michael@0 452 nsCOMPtr<nsIForm> form;
michael@0 453 if (control) {
michael@0 454 // is this a form and does it have a default submit element?
michael@0 455 if ((form = do_QueryInterface(formElement)) &&
michael@0 456 form->GetDefaultSubmitElement()) {
michael@0 457 willSubmit = true;
michael@0 458 // is this an html form and does it only have a single text input element?
michael@0 459 } else if (formElement && formElement->Tag() == nsGkAtoms::form &&
michael@0 460 formElement->IsHTML() &&
michael@0 461 !static_cast<dom::HTMLFormElement*>(formElement)->
michael@0 462 ImplicitSubmissionIsDisabled()) {
michael@0 463 willSubmit = true;
michael@0 464 }
michael@0 465 }
michael@0 466 context.mActionHint.Assign(
michael@0 467 willSubmit ? (control->GetType() == NS_FORM_INPUT_SEARCH ?
michael@0 468 NS_LITERAL_STRING("search") : NS_LITERAL_STRING("go")) :
michael@0 469 (formElement ?
michael@0 470 NS_LITERAL_STRING("next") : EmptyString()));
michael@0 471 }
michael@0 472 }
michael@0 473
michael@0 474 // XXX I think that we should use nsContentUtils::IsCallerChrome() instead
michael@0 475 // of the process type.
michael@0 476 if (aAction.mCause == InputContextAction::CAUSE_UNKNOWN &&
michael@0 477 XRE_GetProcessType() != GeckoProcessType_Content) {
michael@0 478 aAction.mCause = InputContextAction::CAUSE_UNKNOWN_CHROME;
michael@0 479 }
michael@0 480
michael@0 481 aWidget->SetInputContext(context, aAction);
michael@0 482 if (oldContext.mIMEState.mEnabled == context.mIMEState.mEnabled) {
michael@0 483 return;
michael@0 484 }
michael@0 485
michael@0 486 nsContentUtils::AddScriptRunner(
michael@0 487 new IMEEnabledStateChangedEvent(context.mIMEState.mEnabled));
michael@0 488 }
michael@0 489
michael@0 490 void
michael@0 491 IMEStateManager::EnsureTextCompositionArray()
michael@0 492 {
michael@0 493 if (sTextCompositions) {
michael@0 494 return;
michael@0 495 }
michael@0 496 sTextCompositions = new TextCompositionArray();
michael@0 497 }
michael@0 498
michael@0 499 void
michael@0 500 IMEStateManager::DispatchCompositionEvent(nsINode* aEventTargetNode,
michael@0 501 nsPresContext* aPresContext,
michael@0 502 WidgetEvent* aEvent,
michael@0 503 nsEventStatus* aStatus,
michael@0 504 EventDispatchingCallback* aCallBack)
michael@0 505 {
michael@0 506 MOZ_ASSERT(aEvent->eventStructType == NS_COMPOSITION_EVENT ||
michael@0 507 aEvent->eventStructType == NS_TEXT_EVENT);
michael@0 508 if (!aEvent->mFlags.mIsTrusted || aEvent->mFlags.mPropagationStopped) {
michael@0 509 return;
michael@0 510 }
michael@0 511
michael@0 512 EnsureTextCompositionArray();
michael@0 513
michael@0 514 WidgetGUIEvent* GUIEvent = aEvent->AsGUIEvent();
michael@0 515
michael@0 516 nsRefPtr<TextComposition> composition =
michael@0 517 sTextCompositions->GetCompositionFor(GUIEvent->widget);
michael@0 518 if (!composition) {
michael@0 519 MOZ_ASSERT(GUIEvent->message == NS_COMPOSITION_START);
michael@0 520 composition = new TextComposition(aPresContext, aEventTargetNode, GUIEvent);
michael@0 521 sTextCompositions->AppendElement(composition);
michael@0 522 }
michael@0 523 #ifdef DEBUG
michael@0 524 else {
michael@0 525 MOZ_ASSERT(GUIEvent->message != NS_COMPOSITION_START);
michael@0 526 }
michael@0 527 #endif // #ifdef DEBUG
michael@0 528
michael@0 529 // Dispatch the event on composing target.
michael@0 530 composition->DispatchEvent(GUIEvent, aStatus, aCallBack);
michael@0 531
michael@0 532 // WARNING: the |composition| might have been destroyed already.
michael@0 533
michael@0 534 // Remove the ended composition from the array.
michael@0 535 if (aEvent->message == NS_COMPOSITION_END) {
michael@0 536 TextCompositionArray::index_type i =
michael@0 537 sTextCompositions->IndexOf(GUIEvent->widget);
michael@0 538 if (i != TextCompositionArray::NoIndex) {
michael@0 539 sTextCompositions->ElementAt(i)->Destroy();
michael@0 540 sTextCompositions->RemoveElementAt(i);
michael@0 541 }
michael@0 542 }
michael@0 543 }
michael@0 544
michael@0 545 // static
michael@0 546 nsresult
michael@0 547 IMEStateManager::NotifyIME(IMEMessage aMessage,
michael@0 548 nsIWidget* aWidget)
michael@0 549 {
michael@0 550 NS_ENSURE_TRUE(aWidget, NS_ERROR_INVALID_ARG);
michael@0 551
michael@0 552 nsRefPtr<TextComposition> composition;
michael@0 553 if (sTextCompositions) {
michael@0 554 composition = sTextCompositions->GetCompositionFor(aWidget);
michael@0 555 }
michael@0 556 if (!composition || !composition->IsSynthesizedForTests()) {
michael@0 557 switch (aMessage) {
michael@0 558 case NOTIFY_IME_OF_CURSOR_POS_CHANGED:
michael@0 559 return aWidget->NotifyIME(IMENotification(aMessage));
michael@0 560 case REQUEST_TO_COMMIT_COMPOSITION:
michael@0 561 case REQUEST_TO_CANCEL_COMPOSITION:
michael@0 562 case NOTIFY_IME_OF_COMPOSITION_UPDATE:
michael@0 563 return composition ?
michael@0 564 aWidget->NotifyIME(IMENotification(aMessage)) : NS_OK;
michael@0 565 default:
michael@0 566 MOZ_CRASH("Unsupported notification");
michael@0 567 }
michael@0 568 MOZ_CRASH(
michael@0 569 "Failed to handle the notification for non-synthesized composition");
michael@0 570 }
michael@0 571
michael@0 572 // If the composition is synthesized events for automated tests, we should
michael@0 573 // dispatch composition events for emulating the native composition behavior.
michael@0 574 // NOTE: The dispatched events are discarded if it's not safe to run script.
michael@0 575 switch (aMessage) {
michael@0 576 case REQUEST_TO_COMMIT_COMPOSITION: {
michael@0 577 nsCOMPtr<nsIWidget> widget(aWidget);
michael@0 578 nsEventStatus status = nsEventStatus_eIgnore;
michael@0 579 if (!composition->LastData().IsEmpty()) {
michael@0 580 WidgetTextEvent textEvent(true, NS_TEXT_TEXT, widget);
michael@0 581 textEvent.theText = composition->LastData();
michael@0 582 textEvent.mFlags.mIsSynthesizedForTests = true;
michael@0 583 widget->DispatchEvent(&textEvent, status);
michael@0 584 if (widget->Destroyed()) {
michael@0 585 return NS_OK;
michael@0 586 }
michael@0 587 }
michael@0 588
michael@0 589 status = nsEventStatus_eIgnore;
michael@0 590 WidgetCompositionEvent endEvent(true, NS_COMPOSITION_END, widget);
michael@0 591 endEvent.data = composition->LastData();
michael@0 592 endEvent.mFlags.mIsSynthesizedForTests = true;
michael@0 593 widget->DispatchEvent(&endEvent, status);
michael@0 594
michael@0 595 return NS_OK;
michael@0 596 }
michael@0 597 case REQUEST_TO_CANCEL_COMPOSITION: {
michael@0 598 nsCOMPtr<nsIWidget> widget(aWidget);
michael@0 599 nsEventStatus status = nsEventStatus_eIgnore;
michael@0 600 if (!composition->LastData().IsEmpty()) {
michael@0 601 WidgetCompositionEvent updateEvent(true, NS_COMPOSITION_UPDATE, widget);
michael@0 602 updateEvent.data = composition->LastData();
michael@0 603 updateEvent.mFlags.mIsSynthesizedForTests = true;
michael@0 604 widget->DispatchEvent(&updateEvent, status);
michael@0 605 if (widget->Destroyed()) {
michael@0 606 return NS_OK;
michael@0 607 }
michael@0 608
michael@0 609 status = nsEventStatus_eIgnore;
michael@0 610 WidgetTextEvent textEvent(true, NS_TEXT_TEXT, widget);
michael@0 611 textEvent.theText = composition->LastData();
michael@0 612 textEvent.mFlags.mIsSynthesizedForTests = true;
michael@0 613 widget->DispatchEvent(&textEvent, status);
michael@0 614 if (widget->Destroyed()) {
michael@0 615 return NS_OK;
michael@0 616 }
michael@0 617 }
michael@0 618
michael@0 619 status = nsEventStatus_eIgnore;
michael@0 620 WidgetCompositionEvent endEvent(true, NS_COMPOSITION_END, widget);
michael@0 621 endEvent.data = composition->LastData();
michael@0 622 endEvent.mFlags.mIsSynthesizedForTests = true;
michael@0 623 widget->DispatchEvent(&endEvent, status);
michael@0 624
michael@0 625 return NS_OK;
michael@0 626 }
michael@0 627 default:
michael@0 628 return NS_OK;
michael@0 629 }
michael@0 630 }
michael@0 631
michael@0 632 // static
michael@0 633 nsresult
michael@0 634 IMEStateManager::NotifyIME(IMEMessage aMessage,
michael@0 635 nsPresContext* aPresContext)
michael@0 636 {
michael@0 637 NS_ENSURE_TRUE(aPresContext, NS_ERROR_INVALID_ARG);
michael@0 638
michael@0 639 nsIWidget* widget = aPresContext->GetRootWidget();
michael@0 640 if (!widget) {
michael@0 641 return NS_ERROR_NOT_AVAILABLE;
michael@0 642 }
michael@0 643 return NotifyIME(aMessage, widget);
michael@0 644 }
michael@0 645
michael@0 646 bool
michael@0 647 IMEStateManager::IsEditable(nsINode* node)
michael@0 648 {
michael@0 649 if (node->IsEditable()) {
michael@0 650 return true;
michael@0 651 }
michael@0 652 // |node| might be readwrite (for example, a text control)
michael@0 653 if (node->IsElement() &&
michael@0 654 node->AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) {
michael@0 655 return true;
michael@0 656 }
michael@0 657 return false;
michael@0 658 }
michael@0 659
michael@0 660 nsINode*
michael@0 661 IMEStateManager::GetRootEditableNode(nsPresContext* aPresContext,
michael@0 662 nsIContent* aContent)
michael@0 663 {
michael@0 664 if (aContent) {
michael@0 665 nsINode* root = nullptr;
michael@0 666 nsINode* node = aContent;
michael@0 667 while (node && IsEditable(node)) {
michael@0 668 root = node;
michael@0 669 node = node->GetParentNode();
michael@0 670 }
michael@0 671 return root;
michael@0 672 }
michael@0 673 if (aPresContext) {
michael@0 674 nsIDocument* document = aPresContext->Document();
michael@0 675 if (document && document->IsEditable()) {
michael@0 676 return document;
michael@0 677 }
michael@0 678 }
michael@0 679 return nullptr;
michael@0 680 }
michael@0 681
michael@0 682 bool
michael@0 683 IMEStateManager::IsEditableIMEState(nsIWidget* aWidget)
michael@0 684 {
michael@0 685 switch (aWidget->GetInputContext().mIMEState.mEnabled) {
michael@0 686 case IMEState::ENABLED:
michael@0 687 case IMEState::PASSWORD:
michael@0 688 return true;
michael@0 689 case IMEState::PLUGIN:
michael@0 690 case IMEState::DISABLED:
michael@0 691 return false;
michael@0 692 default:
michael@0 693 MOZ_CRASH("Unknown IME enable state");
michael@0 694 }
michael@0 695 }
michael@0 696
michael@0 697 void
michael@0 698 IMEStateManager::DestroyTextStateManager()
michael@0 699 {
michael@0 700 if (!sActiveIMEContentObserver) {
michael@0 701 return;
michael@0 702 }
michael@0 703
michael@0 704 nsRefPtr<IMEContentObserver> tsm;
michael@0 705 tsm.swap(sActiveIMEContentObserver);
michael@0 706 tsm->Destroy();
michael@0 707 }
michael@0 708
michael@0 709 void
michael@0 710 IMEStateManager::CreateIMEContentObserver()
michael@0 711 {
michael@0 712 if (sActiveIMEContentObserver) {
michael@0 713 NS_WARNING("text state observer has been there already");
michael@0 714 MOZ_ASSERT(sActiveIMEContentObserver->IsManaging(sPresContext, sContent));
michael@0 715 return;
michael@0 716 }
michael@0 717
michael@0 718 nsCOMPtr<nsIWidget> widget = sPresContext->GetRootWidget();
michael@0 719 if (!widget) {
michael@0 720 return; // Sometimes, there are no widgets.
michael@0 721 }
michael@0 722
michael@0 723 // If it's not text ediable, we don't need to create IMEContentObserver.
michael@0 724 if (!IsEditableIMEState(widget)) {
michael@0 725 return;
michael@0 726 }
michael@0 727
michael@0 728 static bool sInitializeIsTestingIME = true;
michael@0 729 if (sInitializeIsTestingIME) {
michael@0 730 Preferences::AddBoolVarCache(&sIsTestingIME, "test.IME", false);
michael@0 731 sInitializeIsTestingIME = false;
michael@0 732 }
michael@0 733
michael@0 734 sActiveIMEContentObserver = new IMEContentObserver();
michael@0 735 NS_ADDREF(sActiveIMEContentObserver);
michael@0 736
michael@0 737 // IMEContentObserver::Init() might create another IMEContentObserver
michael@0 738 // instance. So, sActiveIMEContentObserver would be replaced with new one.
michael@0 739 // We should hold the current instance here.
michael@0 740 nsRefPtr<IMEContentObserver> kungFuDeathGrip(sActiveIMEContentObserver);
michael@0 741 sActiveIMEContentObserver->Init(widget, sPresContext, sContent);
michael@0 742 }
michael@0 743
michael@0 744 nsresult
michael@0 745 IMEStateManager::GetFocusSelectionAndRoot(nsISelection** aSelection,
michael@0 746 nsIContent** aRootContent)
michael@0 747 {
michael@0 748 if (!sActiveIMEContentObserver) {
michael@0 749 return NS_ERROR_NOT_AVAILABLE;
michael@0 750 }
michael@0 751 return sActiveIMEContentObserver->GetSelectionAndRoot(aSelection,
michael@0 752 aRootContent);
michael@0 753 }
michael@0 754
michael@0 755 // static
michael@0 756 already_AddRefed<TextComposition>
michael@0 757 IMEStateManager::GetTextCompositionFor(nsIWidget* aWidget)
michael@0 758 {
michael@0 759 if (!sTextCompositions) {
michael@0 760 return nullptr;
michael@0 761 }
michael@0 762 nsRefPtr<TextComposition> textComposition =
michael@0 763 sTextCompositions->GetCompositionFor(aWidget);
michael@0 764 return textComposition.forget();
michael@0 765 }
michael@0 766
michael@0 767 // static
michael@0 768 already_AddRefed<TextComposition>
michael@0 769 IMEStateManager::GetTextCompositionFor(WidgetGUIEvent* aEvent)
michael@0 770 {
michael@0 771 MOZ_ASSERT(aEvent->AsCompositionEvent() || aEvent->AsTextEvent() ||
michael@0 772 aEvent->AsKeyboardEvent(),
michael@0 773 "aEvent has to be WidgetCompositionEvent, WidgetTextEvent or "
michael@0 774 "WidgetKeyboardEvent");
michael@0 775 return GetTextCompositionFor(aEvent->widget);
michael@0 776 }
michael@0 777
michael@0 778 } // namespace mozilla

mercurial