From 8acd6ae7f53aee8f8d7c94f21332e758f05747d7 Mon Sep 17 00:00:00 2001 From: Masayuki Nakano Date: Wed, 26 Sep 2012 14:47:45 +0900 Subject: [PATCH] Bug 705057 part.1 Ensure a set of composition events is fired on same content r=smaug --- content/events/src/Makefile.in | 1 + content/events/src/TextComposition.cpp | 136 ++++++++++++++++++ content/events/src/TextComposition.h | 103 +++++++++++++ content/events/src/nsEventStateManager.cpp | 2 + content/events/src/nsIMEStateManager.cpp | 108 +++++++++++++- content/events/src/nsIMEStateManager.h | 28 ++++ dom/base/nsFocusManager.cpp | 7 - layout/base/nsPresContext.cpp | 9 ++ layout/base/nsPresContext.h | 10 ++ layout/base/nsPresShell.cpp | 43 +++--- layout/base/tests/bug746993-1.html | 13 +- layout/build/nsLayoutStatics.cpp | 2 + .../mochitest/tests/SimpleTest/EventUtils.js | 39 ++++- .../tests/test_sanityEventUtils.html | 36 +++-- 14 files changed, 479 insertions(+), 58 deletions(-) create mode 100644 content/events/src/TextComposition.cpp create mode 100644 content/events/src/TextComposition.h diff --git a/content/events/src/Makefile.in b/content/events/src/Makefile.in index 49f3f0eb1c07..57552b42b14f 100644 --- a/content/events/src/Makefile.in +++ b/content/events/src/Makefile.in @@ -61,6 +61,7 @@ CPPSRCS = \ nsDOMTouchEvent.cpp \ nsDOMCompositionEvent.cpp \ DOMWheelEvent.cpp \ + TextComposition.cpp \ $(NULL) ifdef MOZ_B2G_RIL diff --git a/content/events/src/TextComposition.cpp b/content/events/src/TextComposition.cpp new file mode 100644 index 000000000000..fc79270f5d96 --- /dev/null +++ b/content/events/src/TextComposition.cpp @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TextComposition.h" +#include "nsContentUtils.h" +#include "nsEventDispatcher.h" +#include "nsGUIEvent.h" +#include "nsIPresShell.h" +#include "nsIWidget.h" +#include "nsPresContext.h" + +namespace mozilla { + +/****************************************************************************** + * TextComposition + ******************************************************************************/ + +TextComposition::TextComposition(nsPresContext* aPresContext, + nsINode* aNode, + nsGUIEvent* aEvent) : + mPresContext(aPresContext), mNode(aNode), + // temporarily, we should assume that one native IME context is per native + // widget. + mNativeContext(aEvent->widget) +{ +} + +TextComposition::TextComposition(const TextComposition& aOther) +{ + mNativeContext = aOther.mNativeContext; + mPresContext = aOther.mPresContext; + mNode = aOther.mNode; +} + +bool +TextComposition::MatchesNativeContext(nsIWidget* aWidget) const +{ + // temporarily, we should assume that one native IME context is per one + // native widget. + return mNativeContext == static_cast(aWidget); +} + +bool +TextComposition::MatchesEventTarget(nsPresContext* aPresContext, + nsINode* aNode) const +{ + return mPresContext == aPresContext && mNode == aNode; +} + +void +TextComposition::DispatchEvent(nsGUIEvent* aEvent, + nsEventStatus* aStatus, + nsDispatchingCallback* aCallBack) +{ + nsEventDispatcher::Dispatch(mNode, mPresContext, + aEvent, nullptr, aStatus, aCallBack); +} + +/****************************************************************************** + * TextCompositionArray + ******************************************************************************/ + +TextCompositionArray::index_type +TextCompositionArray::IndexOf(nsIWidget* aWidget) +{ + for (index_type i = Length(); i > 0; --i) { + if (ElementAt(i - 1).MatchesNativeContext(aWidget)) { + return i - 1; + } + } + return NoIndex; +} + +TextCompositionArray::index_type +TextCompositionArray::IndexOf(nsPresContext* aPresContext) +{ + for (index_type i = Length(); i > 0; --i) { + if (ElementAt(i - 1).GetPresContext() == aPresContext) { + return i - 1; + } + } + return NoIndex; +} + +TextCompositionArray::index_type +TextCompositionArray::IndexOf(nsPresContext* aPresContext, + nsINode* aNode) +{ + index_type index = IndexOf(aPresContext); + if (index == NoIndex) { + return NoIndex; + } + nsINode* node = ElementAt(index).GetEventTargetNode(); + return node == aNode ? index : NoIndex; +} + +TextComposition* +TextCompositionArray::GetCompositionFor(nsIWidget* aWidget) +{ + index_type i = IndexOf(aWidget); + return i != NoIndex ? &ElementAt(i) : nullptr; +} + +TextComposition* +TextCompositionArray::GetCompositionFor(nsPresContext* aPresContext) +{ + index_type i = IndexOf(aPresContext); + return i != NoIndex ? &ElementAt(i) : nullptr; +} + +TextComposition* +TextCompositionArray::GetCompositionFor(nsPresContext* aPresContext, + nsINode* aNode) +{ + index_type i = IndexOf(aPresContext, aNode); + return i != NoIndex ? &ElementAt(i) : nullptr; +} + +TextComposition* +TextCompositionArray::GetCompositionInContent(nsPresContext* aPresContext, + nsIContent* aContent) +{ + // There should be only one composition per content object. + for (index_type i = Length(); i > 0; --i) { + nsINode* node = ElementAt(i - 1).GetEventTargetNode(); + if (node && nsContentUtils::ContentIsDescendantOf(node, aContent)) { + return &ElementAt(i - 1); + } + } + return nullptr; +} + +} // namespace mozilla diff --git a/content/events/src/TextComposition.h b/content/events/src/TextComposition.h new file mode 100644 index 000000000000..2c89d7b2cf1d --- /dev/null +++ b/content/events/src/TextComposition.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_TextComposition_h +#define mozilla_TextComposition_h + +#include "nsCOMPtr.h" +#include "nsEvent.h" +#include "nsINode.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" + +class nsCompositionEvent; +class nsDispatchingCallback; +class nsIMEStateManager; +class nsIWidget; +class nsPresContext; + +namespace mozilla { + +/** + * TextComposition represents a text composition. This class stores the + * composition event target and its presContext. At dispatching the event via + * this class, the instances use the stored event target. + */ + +class TextComposition MOZ_FINAL +{ + friend class ::nsIMEStateManager; +public: + TextComposition(nsPresContext* aPresContext, + nsINode* aNode, + nsGUIEvent* aEvent); + + TextComposition(const TextComposition& aOther); + + ~TextComposition() + { + // WARNING: mPresContext may be destroying, so, be careful if you touch it. + } + + nsPresContext* GetPresContext() const { return mPresContext; } + nsINode* GetEventTargetNode() const { return mNode; } + + bool MatchesNativeContext(nsIWidget* aWidget) const; + bool MatchesEventTarget(nsPresContext* aPresContext, + nsINode* aNode) const; + +private: + // This class holds nsPresContext weak. This instance shouldn't block + // destroying it. When the presContext is being destroyed, it's notified to + // nsIMEStateManager::OnDestroyPresContext(), and then, it destroy + // this instance. + nsPresContext* mPresContext; + nsCOMPtr mNode; + + // mNativeContext stores a opaque pointer. This works as the "ID" for this + // composition. Don't access the instance, it may not be available. + void* mNativeContext; + + // Hide the default constructor + TextComposition() {} + + /** + * DispatchEvent() dispatches the aEvent to the mContent synchronously. + * The caller must ensure that it's safe to dispatch the event. + */ + void DispatchEvent(nsGUIEvent* aEvent, + nsEventStatus* aStatus, + nsDispatchingCallback* aCallBack); +}; + +/** + * TextCompositionArray manages the instances of TextComposition class. + * Managing with array is enough because only one composition is typically + * there. Even if user switches native IME context, it's very rare that + * second or more composition is started. + * It's assumed that this is used by nsIMEStateManager for storing all active + * compositions in the process. If the instance is it, each TextComposition + * in the array can be destroyed by calling some methods of itself. + */ + +class TextCompositionArray MOZ_FINAL : public nsAutoTArray +{ +public: + index_type IndexOf(nsIWidget* aWidget); + index_type IndexOf(nsPresContext* aPresContext); + index_type IndexOf(nsPresContext* aPresContext, nsINode* aNode); + + TextComposition* GetCompositionFor(nsIWidget* aWidget); + TextComposition* GetCompositionFor(nsPresContext* aPresContext); + TextComposition* GetCompositionFor(nsPresContext* aPresContext, + nsINode* aNode); + TextComposition* GetCompositionInContent(nsPresContext* aPresContext, + nsIContent* aContent); +}; + +} // namespace mozilla + +#endif // #ifndef mozilla_TextComposition_h diff --git a/content/events/src/nsEventStateManager.cpp b/content/events/src/nsEventStateManager.cpp index bc55d3d6360f..b76f7d1a1555 100644 --- a/content/events/src/nsEventStateManager.cpp +++ b/content/events/src/nsEventStateManager.cpp @@ -4886,6 +4886,8 @@ nsEventStateManager::ContentRemoved(nsIDocument* aDocument, nsIContent* aContent element->LeaveLink(element->GetPresContext()); } + nsIMEStateManager::OnRemoveContent(mPresContext, aContent); + // inform the focus manager that the content is being removed. If this // content is focused, the focus will be removed without firing events. nsFocusManager* fm = nsFocusManager::GetFocusManager(); diff --git a/content/events/src/nsIMEStateManager.cpp b/content/events/src/nsIMEStateManager.cpp index ad1fceb6fde9..02482d4180d7 100644 --- a/content/events/src/nsIMEStateManager.cpp +++ b/content/events/src/nsIMEStateManager.cpp @@ -34,7 +34,10 @@ #include "nsIForm.h" #include "nsHTMLFormElement.h" #include "mozilla/Attributes.h" +#include "nsEventDispatcher.h" +#include "TextComposition.h" +using namespace mozilla; using namespace mozilla::widget; /******************************************************************/ @@ -47,11 +50,33 @@ bool nsIMEStateManager::sInstalledMenuKeyboardListener = false; bool nsIMEStateManager::sInSecureInputMode = false; nsTextStateManager* nsIMEStateManager::sTextStateObserver = nullptr; +TextCompositionArray* nsIMEStateManager::sTextCompositions = nullptr; + +void +nsIMEStateManager::Shutdown() +{ + MOZ_ASSERT(!sTextCompositions || !sTextCompositions->Length()); + delete sTextCompositions; + sTextCompositions = nullptr; +} nsresult nsIMEStateManager::OnDestroyPresContext(nsPresContext* aPresContext) { NS_ENSURE_ARG_POINTER(aPresContext); + + // First, if there is a composition in the aPresContext, clean up it. + if (sTextCompositions) { + TextCompositionArray::index_type i = + sTextCompositions->IndexOf(aPresContext); + if (i != TextCompositionArray::NoIndex) { + // there should be only one composition per presContext object. + sTextCompositions->RemoveElementAt(i); + MOZ_ASSERT(sTextCompositions->IndexOf(aPresContext) == + TextCompositionArray::NoIndex); + } + } + if (aPresContext != sPresContext) return NS_OK; nsCOMPtr widget = GetWidget(sPresContext); @@ -72,17 +97,35 @@ nsIMEStateManager::OnRemoveContent(nsPresContext* aPresContext, nsIContent* aContent) { NS_ENSURE_ARG_POINTER(aPresContext); + + // First, if there is a composition in the aContent, clean up it. + if (sTextCompositions) { + TextComposition* compositionInContent = + sTextCompositions->GetCompositionInContent(aPresContext, aContent); + if (compositionInContent) { + // Try resetting the native IME state. Be aware, typically, this method + // is called during the content being removed. Then, the native + // composition events which are caused by following APIs are ignored due + // to unsafe to run script (in PresShell::HandleEvent()). + nsCOMPtr widget = aPresContext->GetNearestWidget(); + if (widget) { + nsresult rv = widget->CancelIMEComposition(); + if (NS_FAILED(rv)) { + widget->ResetInputState(); + } + // WARNING: the |compositionInContent| may have been destroyed. + } + } + } + if (!sPresContext || !sContent || - aPresContext != sPresContext || - aContent != sContent) + !nsContentUtils::ContentIsDescendantOf(sContent, aContent)) { return NS_OK; + } // Current IME transaction should commit nsCOMPtr widget = GetWidget(sPresContext); if (widget) { - nsresult rv = widget->CancelIMEComposition(); - if (NS_FAILED(rv)) - widget->ResetInputState(); IMEState newState = GetNewIMEState(sPresContext, nullptr); InputContextAction action(InputContextAction::CAUSE_UNKNOWN, InputContextAction::LOST_FOCUS); @@ -384,6 +427,61 @@ nsIMEStateManager::GetWidget(nsPresContext* aPresContext) return widget; } +void +nsIMEStateManager::EnsureTextCompositionArray() +{ + if (sTextCompositions) { + return; + } + sTextCompositions = new TextCompositionArray(); +} + +void +nsIMEStateManager::DispatchCompositionEvent(nsINode* aEventTargetNode, + nsPresContext* aPresContext, + nsEvent* aEvent, + nsEventStatus* aStatus, + nsDispatchingCallback* aCallBack) +{ + MOZ_ASSERT(aEvent->eventStructType == NS_COMPOSITION_EVENT || + aEvent->eventStructType == NS_TEXT_EVENT); + if (!NS_IS_TRUSTED_EVENT(aEvent) || + (aEvent->flags & NS_EVENT_FLAG_STOP_DISPATCH) != 0) { + return; + } + + EnsureTextCompositionArray(); + + nsGUIEvent* GUIEvent = static_cast(aEvent); + + TextComposition* composition = + sTextCompositions->GetCompositionFor(GUIEvent->widget); + if (!composition) { + MOZ_ASSERT(GUIEvent->message == NS_COMPOSITION_START); + TextComposition newComposition(aPresContext, aEventTargetNode, GUIEvent); + composition = sTextCompositions->AppendElement(newComposition); + } +#ifdef DEBUG + else { + MOZ_ASSERT(GUIEvent->message != NS_COMPOSITION_START); + } +#endif // #ifdef DEBUG + + // Dispatch the event on composing target. + composition->DispatchEvent(GUIEvent, aStatus, aCallBack); + + // WARNING: the |composition| might have been destroyed already. + + // Remove the ended composition from the array. + if (aEvent->message == NS_COMPOSITION_END) { + TextCompositionArray::index_type i = + sTextCompositions->IndexOf(GUIEvent->widget); + if (i != TextCompositionArray::NoIndex) { + sTextCompositions->RemoveElementAt(i); + } + } +} + // nsTextStateManager notifies widget of any text and selection changes // in the currently focused editor diff --git a/content/events/src/nsIMEStateManager.h b/content/events/src/nsIMEStateManager.h index d25ead898f2a..47a6b2cd8d0d 100644 --- a/content/events/src/nsIMEStateManager.h +++ b/content/events/src/nsIMEStateManager.h @@ -9,13 +9,19 @@ #include "nscore.h" #include "nsIWidget.h" +class nsDispatchingCallback; class nsIContent; class nsIDOMMouseEvent; +class nsINode; class nsPIDOMWindow; class nsPresContext; class nsTextStateManager; class nsISelection; +namespace mozilla { +class TextCompositionArray; +} // namespace mozilla + /* * IME state manager */ @@ -28,6 +34,8 @@ protected: typedef mozilla::widget::InputContextAction InputContextAction; public: + static void Shutdown(); + static nsresult OnDestroyPresContext(nsPresContext* aPresContext); static nsresult OnRemoveContent(nsPresContext* aPresContext, nsIContent* aContent); @@ -75,6 +83,19 @@ public: nsIContent* aContent, nsIDOMMouseEvent* aMouseEvent); + /** + * All DOM composition events and DOM text events must be dispatched via + * DispatchCompositionEvent() for storing the composition target + * and ensuring a set of composition events must be fired the stored target. + * If the stored composition event target is destroying, this removes the + * stored composition automatically. + */ + static void DispatchCompositionEvent(nsINode* aEventTargetNode, + nsPresContext* aPresContext, + nsEvent* aEvent, + nsEventStatus* aStatus, + nsDispatchingCallback* aCallBack); + protected: static nsresult OnChangeFocusInternal(nsPresContext* aPresContext, nsIContent* aContent, @@ -87,6 +108,7 @@ protected: nsIContent* aContent); static nsIWidget* GetWidget(nsPresContext* aPresContext); + static void EnsureTextCompositionArray(); static nsIContent* sContent; static nsPresContext* sPresContext; @@ -94,6 +116,12 @@ protected: static bool sInSecureInputMode; static nsTextStateManager* sTextStateObserver; + + // All active compositions in the process are stored by this array. + // When you get an item of this array and use it, please be careful. + // The instances in this array can be destroyed automatically if you do + // something to cause committing or canceling the composition. + static mozilla::TextCompositionArray* sTextCompositions; }; #endif // nsIMEStateManager_h__ diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp index a242c092a852..15e1f53ebeeb 100644 --- a/dom/base/nsFocusManager.cpp +++ b/dom/base/nsFocusManager.cpp @@ -812,13 +812,6 @@ nsFocusManager::ContentRemoved(nsIDocument* aDocument, nsIContent* aContent) bool shouldShowFocusRing = window->ShouldShowFocusRing(); window->SetFocusedNode(nullptr); - nsCOMPtr docShell = window->GetDocShell(); - if (docShell) { - nsCOMPtr presShell; - docShell->GetPresShell(getter_AddRefs(presShell)); - nsIMEStateManager::OnRemoveContent(presShell->GetPresContext(), content); - } - // if this window is currently focused, clear the global focused // element as well, but don't fire any events. if (window == mFocusedWindow) { diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp index 22b8a8f63960..825be762346f 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -1170,6 +1170,15 @@ nsPresContext::GetToplevelContentDocumentPresContext() } } +nsIWidget* +nsPresContext::GetNearestWidget(nsPoint* aOffset) +{ + NS_ENSURE_TRUE(mShell, nullptr); + nsIFrame* frame = mShell->GetRootFrame(); + NS_ENSURE_TRUE(frame, nullptr); + return frame->GetView()->GetNearestWidget(aOffset); +} + // We may want to replace this with something faster, maybe caching the root prescontext nsRootPresContext* nsPresContext::GetRootPresContext() diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h index d0e172ae30e1..cdc5cbda92fc 100644 --- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h @@ -190,6 +190,16 @@ public: */ nsPresContext* GetToplevelContentDocumentPresContext(); + /** + * Returns the nearest widget for the root frame of this. + * + * @param aOffset If non-null the offset from the origin of the root + * frame's view to the widget's origin (usually positive) + * expressed in appunits of this will be returned in + * aOffset. + */ + nsIWidget* GetNearestWidget(nsPoint* aOffset = nullptr); + /** * Return the presentation context for the root of the view manager * hierarchy that contains this presentation context, or nullptr if it can't diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 16059f7c244f..79137790943e 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -73,6 +73,7 @@ #include "nsViewsCID.h" #include "nsFrameManager.h" #include "nsEventStateManager.h" +#include "nsIMEStateManager.h" #include "nsXPCOM.h" #include "nsISupportsPrimitives.h" #include "nsILayoutHistoryState.h" @@ -6435,23 +6436,33 @@ PresShell::HandleEventInternal(nsEvent* aEvent, nsEventStatus* aStatus) nsPresShellEventCB eventCB(this); if (aEvent->eventStructType == NS_TOUCH_EVENT) { DispatchTouchEvent(aEvent, aStatus, &eventCB, touchIsNew); - } - else if (mCurrentEventContent) { - nsEventDispatcher::Dispatch(mCurrentEventContent, mPresContext, - aEvent, nullptr, aStatus, &eventCB); - } - else { - nsCOMPtr targetContent; - if (mCurrentEventFrame) { - rv = mCurrentEventFrame->GetContentForEvent(aEvent, - getter_AddRefs(targetContent)); + } else { + nsCOMPtr eventTarget = mCurrentEventContent.get(); + nsPresShellEventCB* eventCBPtr = &eventCB; + if (!eventTarget) { + nsCOMPtr targetContent; + if (mCurrentEventFrame) { + rv = mCurrentEventFrame-> + GetContentForEvent(aEvent, getter_AddRefs(targetContent)); + } + if (NS_SUCCEEDED(rv) && targetContent) { + eventTarget = do_QueryInterface(targetContent); + } else if (mDocument) { + eventTarget = do_QueryInterface(mDocument); + // If we don't have any content, the callback wouldn't probably + // do nothing. + eventCBPtr = nullptr; + } } - if (NS_SUCCEEDED(rv) && targetContent) { - nsEventDispatcher::Dispatch(targetContent, mPresContext, aEvent, - nullptr, aStatus, &eventCB); - } else if (mDocument) { - nsEventDispatcher::Dispatch(mDocument, mPresContext, aEvent, - nullptr, aStatus, nullptr); + if (eventTarget) { + if (aEvent->eventStructType == NS_COMPOSITION_EVENT || + aEvent->eventStructType == NS_TEXT_EVENT) { + nsIMEStateManager::DispatchCompositionEvent(eventTarget, + mPresContext, aEvent, aStatus, eventCBPtr); + } else { + nsEventDispatcher::Dispatch(eventTarget, mPresContext, + aEvent, nullptr, aStatus, eventCBPtr); + } } } } diff --git a/layout/base/tests/bug746993-1.html b/layout/base/tests/bug746993-1.html index eb75d7151730..20fe20244e62 100644 --- a/layout/base/tests/bug746993-1.html +++ b/layout/base/tests/bug746993-1.html @@ -13,18 +13,7 @@ iframe.focus(); doc.body.focus(); win.getSelection().collapse(doc.body, 0); - synthesizeText( - { "composition": - { - "string": "Here's some text.", - "clauses": - [ - { "length": 17, "attr": COMPOSITION_ATTR_RAWINPUT } - ] - }, - "caret": { "start": 2, "length": 0 } - } - ) + sendString("Here's some text."); synthesizeKey("VK_RETURN", {}); document.documentElement.removeAttribute("class"); } diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp index 8666ce507881..1c0e1eee7f3f 100644 --- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -103,6 +103,7 @@ #include "nsCookieService.h" #include "nsApplicationCacheService.h" #include "mozilla/dom/time/DateCacheCleaner.h" +#include "nsIMEStateManager.h" extern void NS_ShutdownChainItemPool(); @@ -284,6 +285,7 @@ nsLayoutStatics::Shutdown() txMozillaXSLTProcessor::Shutdown(); nsDOMAttribute::Shutdown(); nsEventListenerManager::Shutdown(); + nsIMEStateManager::Shutdown(); nsComputedDOMStyle::Shutdown(); nsCSSParser::Shutdown(); nsCSSRuleProcessor::Shutdown(); diff --git a/testing/mochitest/tests/SimpleTest/EventUtils.js b/testing/mochitest/tests/SimpleTest/EventUtils.js index a766c09e1f3c..e45dbd22b16f 100644 --- a/testing/mochitest/tests/SimpleTest/EventUtils.js +++ b/testing/mochitest/tests/SimpleTest/EventUtils.js @@ -86,20 +86,47 @@ function sendMouseEvent(aEvent, aTarget, aWindow) { * chars (sends the right charcode, and sends a shift key for uppercase chars). * No other modifiers are handled at this point. * - * For now this method only works for English letters (lower and upper case) - * and the digits 0-9. + * For now this method only works for ASCII characters and emulates the shift + * key state on US keyboard layout. */ function sendChar(aChar, aWindow) { - // DOM event charcodes match ASCII (JS charcodes) for a-zA-Z0-9. - var hasShift = (aChar == aChar.toUpperCase()); + var hasShift; + // Emulate US keyboard layout for the shiftKey state. + switch (aChar) { + case "!": + case "@": + case "#": + case "$": + case "%": + case "^": + case "&": + case "*": + case "(": + case ")": + case "_": + case "+": + case "{": + case "}": + case ":": + case "\"": + case "|": + case "<": + case ">": + case "?": + hasShift = true; + break; + default: + hasShift = (aChar == aChar.toUpperCase()); + break; + } synthesizeKey(aChar, { shiftKey: hasShift }, aWindow); } /** * Send the string aStr to the focused element. * - * For now this method only works for English letters (lower and upper case) - * and the digits 0-9. + * For now this method only works for ASCII characters and emulates the shift + * key state on US keyboard layout. */ function sendString(aStr, aWindow) { for (var i = 0; i < aStr.length; ++i) { diff --git a/testing/mochitest/tests/test_sanityEventUtils.html b/testing/mochitest/tests/test_sanityEventUtils.html index d11eaa6b7782..454f62c6b412 100644 --- a/testing/mochitest/tests/test_sanityEventUtils.html +++ b/testing/mochitest/tests/test_sanityEventUtils.html @@ -115,6 +115,7 @@ function starttest() { $("testKeyEvent").value = ""; /* test synthesizeComposition */ + $("textBoxB").focus(); check = false; window.addEventListener("compositionstart", function() { check = true; }, false); synthesizeComposition({ type: "compositionstart" }); @@ -122,30 +123,41 @@ function starttest() { check = false; window.addEventListener("compositionupdate", function() { check = true; }, false); - synthesizeComposition({ type: "compositionupdate" }); + synthesizeComposition({ type: "compositionupdate", data: "a" }); is(check, true, 'synthesizeComposition() should dispatch compositionupdate'); - + check = false; - window.addEventListener("compositionend", function() { check = true; }, false); - synthesizeComposition({ type: "compositionend" }); - is(check, true, 'synthesizeComposition() should dispatch compositionend'); - check = false; - - $("textBoxB").focus(); - const nsIDOMWindowUtils = SpecialPowers.Ci.nsIDOMWindowUtils; + window.addEventListener("text", function() { check = true; }, false); synthesizeText( { "composition": { "string": "a", "clauses": [ - { "length": 1, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT } + { "length": 1, "attr": COMPOSITION_ATTR_RAWINPUT } ] }, "caret": { "start": 1, "length": 0 } } ); - is(check, false, "synthesizeText shouldn't start or end composition"); - is($("textBoxB").value, "a", "synthesizeText should send text"); + is(check, true, "synthesizeText should dispatch text event"); + + synthesizeText( + { "composition": + { "string": "a", + "clauses": + [ + { "length": 0, "attr": 0 } + ] + }, + "caret": { "start": 1, "length": 0 } + } + ); + + check = false; + window.addEventListener("compositionend", function() { check = true; }, false); + synthesizeComposition({ type: "compositionend", data: "a" }); + is(check, true, 'synthesizeComposition() should dispatch compositionend'); + var querySelectedText = synthesizeQuerySelectedText(); ok(querySelectedText, "query selected text event result is null"); netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); //get permission to check members of querySelectedText