Bug 88831 Support new IME API "Text Services Framework" from Office XP and Windows XP r=masayuki+peterv, sr=roc
authorJim Chen <chenn@email.uc.edu>
Tue, 10 Feb 2009 20:15:36 +0900
changeset 24823 3cb3ab57d0b3cb3f0b7bdef89455d4b527bb7087
parent 24822 2681688f4cdf98a09ff08d6c26bcadbff3d92fa7
child 24824 2e550e7b8f3c11992e15989dfd4efe442edf3f4f
push id5269
push usermasayuki@d-toybox.com
push dateTue, 10 Feb 2009 11:16:16 +0000
treeherdermozilla-central@3cb3ab57d0b3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmasayuki, roc
bugs88831
milestone1.9.2a1pre
Bug 88831 Support new IME API "Text Services Framework" from Office XP and Windows XP r=masayuki+peterv, sr=roc
content/base/src/nsGenericElement.cpp
content/base/src/nsGkAtomList.h
content/events/src/Makefile.in
content/events/src/nsContentEventHandler.cpp
content/events/src/nsContentEventHandler.h
content/events/src/nsEventStateManager.cpp
content/events/src/nsIMEStateManager.cpp
content/events/src/nsIMEStateManager.h
content/events/src/nsQueryContentEventHandler.cpp
content/events/src/nsQueryContentEventHandler.h
content/html/content/src/nsGenericHTMLElement.h
modules/libpref/src/init/all.js
view/src/nsViewManager.cpp
widget/Makefile.in
widget/public/nsGUIEvent.h
widget/public/nsIWidget.h
widget/src/cocoa/nsChildView.mm
widget/src/windows/Makefile.in
widget/src/windows/nsTextStore.cpp
widget/src/windows/nsTextStore.h
widget/src/windows/nsWindow.cpp
widget/src/windows/nsWindow.h
widget/src/xpwidgets/nsBaseWidget.h
widget/tests/Makefile.in
widget/tests/TestWinTSF.cpp
--- a/content/base/src/nsGenericElement.cpp
+++ b/content/base/src/nsGenericElement.cpp
@@ -113,17 +113,17 @@
 
 #include "jsapi.h"
 
 #include "nsNodeInfoManager.h"
 #include "nsICategoryManager.h"
 #include "nsIDOMNSFeatureFactory.h"
 #include "nsIDOMDocumentType.h"
 #include "nsIDOMUserDataHandler.h"
-#include "nsIDOMNSEditableElement.h"
+#include "nsGenericHTMLElement.h"
 #include "nsIEditor.h"
 #include "nsIEditorDocShell.h"
 #include "nsEventDispatcher.h"
 #include "nsContentCreatorFunctions.h"
 #include "nsIFocusController.h"
 #include "nsIControllers.h"
 #include "nsLayoutUtils.h"
 #include "nsIView.h"
@@ -338,23 +338,25 @@ static nsIContent* GetEditorRootContent(
 }
 
 nsIContent*
 nsINode::GetTextEditorRootContent(nsIEditor** aEditor)
 {
   if (aEditor)
     *aEditor = nsnull;
   for (nsINode* node = this; node; node = node->GetNodeParent()) {
-    nsCOMPtr<nsIDOMNSEditableElement> editableElement(do_QueryInterface(node));
-    if (!editableElement)
+    if (!node->IsNodeOfType(eHTML))
       continue;
 
     nsCOMPtr<nsIEditor> editor;
-    editableElement->GetEditor(getter_AddRefs(editor));
-    NS_ENSURE_TRUE(editor, nsnull);
+    static_cast<nsGenericHTMLElement*>(node)->
+        GetEditorInternal(getter_AddRefs(editor));
+    if (!editor)
+      continue;
+
     nsIContent* rootContent = GetEditorRootContent(editor);
     if (aEditor)
       editor.swap(*aEditor);
     return rootContent;
   }
   return nsnull;
 }
 
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -57,17 +57,19 @@
 #undef small
 #endif 
 
 //---------------------------------------------------------------------------
 // Generic atoms
 //---------------------------------------------------------------------------
 
 GK_ATOM(_empty, "")
+GK_ATOM(moz, "_moz")
 GK_ATOM(mozdirty, "_moz_dirty")
+GK_ATOM(mozeditorbogusnode, "_moz_editor_bogus_node")
 GK_ATOM(mozgeneratedcontentbefore, "_moz_generated_content_before")
 GK_ATOM(mozgeneratedcontentafter, "_moz_generated_content_after")
 GK_ATOM(mozgeneratedcontentimage, "_moz_generated_content_image")
 GK_ATOM(_moz_target, "_moz_target")
 GK_ATOM(menuactive, "_moz-menuactive")
 GK_ATOM(_poundDefault, "#default")
 GK_ATOM(_asterix, "*")
 GK_ATOM(a, "a")
--- a/content/events/src/Makefile.in
+++ b/content/events/src/Makefile.in
@@ -86,17 +86,17 @@ CPPSRCS		= \
 		nsDOMMessageEvent.cpp \
 		nsPrivateTextRange.cpp \
 		nsDOMEventGroup.cpp \
 		nsXMLEventsManager.cpp \
 		nsXMLEventsElement.cpp \
 		nsPLDOMEvent.cpp \
 		nsEventDispatcher.cpp \
 		nsIMEStateManager.cpp \
-		nsQueryContentEventHandler.cpp \
+		nsContentEventHandler.cpp \
 		nsDOMProgressEvent.cpp \
 		nsDOMDataTransfer.cpp \
 		nsDOMNotifyPaintEvent.cpp \
 		nsDOMSimpleGestureEvent.cpp \
 		$(NULL)
 
 # we don't want the shared lib, but we want to force the creation of a static lib.
 FORCE_STATIC_LIB = 1
rename from content/events/src/nsQueryContentEventHandler.cpp
rename to content/events/src/nsContentEventHandler.cpp
--- a/content/events/src/nsQueryContentEventHandler.cpp
+++ b/content/events/src/nsContentEventHandler.cpp
@@ -17,64 +17,70 @@
  *
  * The Initial Developer of the Original Code is
  * Mozilla Japan.
  * Portions created by the Initial Developer are Copyright (C) 2008
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Masayuki Nakano <masayuki@d-toybox.com>
+ *   Ningjie Chen <chenn@email.uc.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either of the GNU General Public License Version 2 or later (the "GPL"),
  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-#include "nsQueryContentEventHandler.h"
+#include "nsContentEventHandler.h"
 #include "nsCOMPtr.h"
 #include "nsPresContext.h"
 #include "nsIPresShell.h"
 #include "nsISelection.h"
 #include "nsIDOMText.h"
 #include "nsIDOMRange.h"
 #include "nsRange.h"
 #include "nsGUIEvent.h"
 #include "nsCaret.h"
 #include "nsFrameSelection.h"
 #include "nsIFrame.h"
 #include "nsIView.h"
 #include "nsIContentIterator.h"
 #include "nsTextFragment.h"
 #include "nsTextFrame.h"
+#include "nsISelectionController.h"
+#include "nsISelectionPrivate.h"
+#include "nsContentUtils.h"
+#include "nsISelection2.h"
+#include "nsIMEStateManager.h"
 
 nsresult NS_NewContentIterator(nsIContentIterator** aInstancePtrResult);
 
 /******************************************************************/
-/* nsQueryContentEventHandler                                     */
+/* nsContentEventHandler                                          */
 /******************************************************************/
 
-nsQueryContentEventHandler::nsQueryContentEventHandler(
+nsContentEventHandler::nsContentEventHandler(
                               nsPresContext* aPresContext) :
   mPresContext(aPresContext),
   mPresShell(aPresContext->GetPresShell()), mSelection(nsnull),
   mFirstSelectedRange(nsnull), mRootContent(nsnull)
 {
 }
 
 nsresult
-nsQueryContentEventHandler::Init(nsQueryContentEvent* aEvent)
+nsContentEventHandler::Init(nsQueryContentEvent* aEvent)
 {
   NS_ASSERTION(aEvent, "aEvent must not be null");
 
   if (mSelection)
     return NS_OK;
 
   aEvent->mSucceeded = PR_FALSE;
 
@@ -112,16 +118,34 @@ nsQueryContentEventHandler::Init(nsQuery
                                   mSelection, &r, &isCollapsed, &view);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(view, NS_ERROR_FAILURE);
   aEvent->mReply.mFocusedWidget = view->GetWidget();
 
   return NS_OK;
 }
 
+// Editor places a bogus BR node under its root content if the editor doesn't
+// have any text. This happens even for single line editors.
+// When we get text content and when we change the selection,
+// we don't want to include the bogus BRs at the end.
+static PRBool IsContentBR(nsIContent* aContent)
+{
+  return aContent->IsNodeOfType(nsINode::eHTML) &&
+         aContent->Tag() == nsGkAtoms::br &&
+         !aContent->AttrValueIs(kNameSpaceID_None,
+                                nsGkAtoms::type,
+                                nsGkAtoms::moz,
+                                eIgnoreCase) &&
+         !aContent->AttrValueIs(kNameSpaceID_None,
+                                nsGkAtoms::mozeditorbogusnode,
+                                nsGkAtoms::_true,
+                                eIgnoreCase);
+}
+
 static void ConvertToNativeNewlines(nsAFlatString& aString)
 {
 #if defined(XP_MACOSX)
   aString.ReplaceSubstring(NS_LITERAL_STRING("\n"), NS_LITERAL_STRING("\r"));
 #elif defined(XP_WIN)
   aString.ReplaceSubstring(NS_LITERAL_STRING("\n"), NS_LITERAL_STRING("\r\n"));
 #endif
 }
@@ -156,18 +180,17 @@ static void AppendSubString(nsAString& a
   text->AppendTo(aString, PRInt32(aXPOffset), PRInt32(aXPLength));
 }
 
 static PRUint32 GetNativeTextLength(nsIContent* aContent)
 {
   nsAutoString str;
   if (aContent->IsNodeOfType(nsINode::eTEXT))
     AppendString(str, aContent);
-  else if (aContent->IsNodeOfType(nsINode::eHTML) &&
-           aContent->Tag() == nsGkAtoms::br)
+  else if (IsContentBR(aContent))
     str.Assign(PRUnichar('\n'));
   ConvertToNativeNewlines(str);
   return str.Length();
 }
 
 static PRUint32 ConvertToXPOffset(nsIContent* aContent, PRUint32 aNativeOffset)
 {
 
@@ -176,19 +199,18 @@ static PRUint32 ConvertToXPOffset(nsICon
   ConvertToNativeNewlines(str);
   NS_ASSERTION(aNativeOffset <= str.Length(),
                "aOffsetForNativeLF is too large!");
   str.Truncate(aNativeOffset);
   ConvertToXPNewlines(str);
   return str.Length();
 }
 
-nsresult
-nsQueryContentEventHandler::GenerateFlatTextContent(nsIRange* aRange,
-                                                    nsAFlatString& aString)
+static nsresult GenerateFlatTextContent(nsIRange* aRange,
+                                        nsAFlatString& aString)
 {
   nsCOMPtr<nsIContentIterator> iter;
   nsresult rv = NS_NewContentIterator(getter_AddRefs(iter));
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ASSERTION(iter, "NS_NewContentIterator succeeded, but the result is null");
   nsCOMPtr<nsIDOMRange> domRange(do_QueryInterface(aRange));
   NS_ASSERTION(domRange, "aRange doesn't have nsIDOMRange!");
   iter->Init(domRange);
@@ -216,37 +238,37 @@ nsQueryContentEventHandler::GenerateFlat
     if (content->IsNodeOfType(nsINode::eTEXT)) {
       if (content == startNode)
         AppendSubString(aString, content, aRange->StartOffset(),
                         content->TextLength() - aRange->StartOffset());
       else if (content == endNode)
         AppendSubString(aString, content, 0, aRange->EndOffset());
       else
         AppendString(aString, content);
-    } else if (content->IsNodeOfType(nsINode::eHTML) &&
-               content->Tag() == nsGkAtoms::br)
+    } else if (IsContentBR(content))
         aString.Append(PRUnichar('\n'));
   }
   ConvertToNativeNewlines(aString);
   return NS_OK;
 }
 
 nsresult
-nsQueryContentEventHandler::ExpandToClusterBoundary(nsIContent* aContent,
+nsContentEventHandler::ExpandToClusterBoundary(nsIContent* aContent,
                                                     PRBool aForward,
                                                     PRUint32* aXPOffset)
 {
-  NS_ASSERTION(*aXPOffset >= 0 && *aXPOffset <= aContent->TextLength(),
-               "offset is out of range.");
-
   // XXX This method assumes that the frame boundaries must be cluster
   // boundaries. It's false, but no problem now, maybe.
   if (!aContent->IsNodeOfType(nsINode::eTEXT) ||
       *aXPOffset == 0 || *aXPOffset == aContent->TextLength())
     return NS_OK;
+
+  NS_ASSERTION(*aXPOffset >= 0 && *aXPOffset <= aContent->TextLength(),
+               "offset is out of range.");
+
   nsCOMPtr<nsFrameSelection> fs = mPresShell->FrameSelection();
   PRInt32 offsetInFrame;
   nsFrameSelection::HINT hint =
     aForward ? nsFrameSelection::HINTLEFT : nsFrameSelection::HINTRIGHT;
   nsIFrame* frame = fs->GetFrameForNodeOffset(aContent, PRInt32(*aXPOffset),
                                               hint, &offsetInFrame);
   if (!frame) {
     // This content doesn't have any frames, we only can check surrogate pair...
@@ -260,25 +282,25 @@ nsQueryContentEventHandler::ExpandToClus
   PRInt32 startOffset, endOffset;
   nsresult rv = frame->GetOffsets(startOffset, endOffset);
   NS_ENSURE_SUCCESS(rv, rv);
   if (*aXPOffset == PRUint32(startOffset) || *aXPOffset == PRUint32(endOffset))
     return NS_OK;
   if (frame->GetType() != nsGkAtoms::textFrame)
     return NS_ERROR_FAILURE;
   nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
-  PRInt32 newOffsetInFrame = offsetInFrame;
+  PRInt32 newOffsetInFrame = *aXPOffset - startOffset;
   newOffsetInFrame += aForward ? -1 : 1;
   textFrame->PeekOffsetCharacter(aForward, &newOffsetInFrame);
   *aXPOffset = startOffset + newOffsetInFrame;
   return NS_OK;
 }
 
 nsresult
-nsQueryContentEventHandler::SetRangeFromFlatTextOffset(
+nsContentEventHandler::SetRangeFromFlatTextOffset(
                               nsIRange* aRange,
                               PRUint32 aNativeOffset,
                               PRUint32 aNativeLength,
                               PRBool aExpandToClusterBoundaries)
 {
   nsCOMPtr<nsIContentIterator> iter;
   nsresult rv = NS_NewContentIterator(getter_AddRefs(iter));
   NS_ENSURE_SUCCESS(rv, rv);
@@ -364,50 +386,61 @@ nsQueryContentEventHandler::SetRangeFrom
     NS_ENSURE_SUCCESS(rv, rv);
   }
   rv = domRange->SetEnd(domNode, PRInt32(mRootContent->GetChildCount()));
   NS_ASSERTION(NS_SUCCEEDED(rv), "nsIDOMRange::SetEnd failed");
   return rv;
 }
 
 nsresult
-nsQueryContentEventHandler::OnQuerySelectedText(nsQueryContentEvent* aEvent)
+nsContentEventHandler::OnQuerySelectedText(nsQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
   if (NS_FAILED(rv))
     return rv;
 
   NS_ASSERTION(aEvent->mReply.mString.IsEmpty(),
                "The reply string must be empty");
 
-  rv = GetFlatTextOffsetOfRange(mFirstSelectedRange, &aEvent->mReply.mOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  PRBool isCollapsed;
-  rv = mSelection->GetIsCollapsed(&isCollapsed);
+  rv = GetFlatTextOffsetOfRange(mRootContent,
+                                mFirstSelectedRange, &aEvent->mReply.mOffset);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (!isCollapsed) {
-    nsCOMPtr<nsIDOMRange> domRange;
-    rv = mSelection->GetRangeAt(0, getter_AddRefs(domRange));
-    NS_ENSURE_SUCCESS(rv, rv);
-    NS_ASSERTION(domRange, "GetRangeAt succeeded, but the result is null");
+  nsCOMPtr<nsIDOMNode> anchorDomNode, focusDomNode;
+  rv = mSelection->GetAnchorNode(getter_AddRefs(anchorDomNode));
+  NS_ENSURE_TRUE(anchorDomNode, NS_ERROR_FAILURE);
+  rv = mSelection->GetFocusNode(getter_AddRefs(focusDomNode));
+  NS_ENSURE_TRUE(focusDomNode, NS_ERROR_FAILURE);
 
-    nsCOMPtr<nsIRange> range(do_QueryInterface(domRange));
-    NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
+  PRInt32 anchorOffset, focusOffset;
+  rv = mSelection->GetAnchorOffset(&anchorOffset);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mSelection->GetFocusOffset(&focusOffset);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsINode> anchorNode(do_QueryInterface(anchorDomNode));
+  nsCOMPtr<nsINode> focusNode(do_QueryInterface(focusDomNode));
+  NS_ENSURE_TRUE(anchorNode && focusNode, NS_ERROR_UNEXPECTED);
+
+  PRInt16 compare = nsContentUtils::ComparePoints(anchorNode, anchorOffset,
+                                                  focusNode, focusOffset);
+  aEvent->mReply.mReversed = compare > 0;
+
+  if (compare) {
+    nsCOMPtr<nsIRange> range = mFirstSelectedRange;
     rv = GenerateFlatTextContent(range, aEvent->mReply.mString);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   aEvent->mSucceeded = PR_TRUE;
   return NS_OK;
 }
 
 nsresult
-nsQueryContentEventHandler::OnQueryTextContent(nsQueryContentEvent* aEvent)
+nsContentEventHandler::OnQueryTextContent(nsQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
   if (NS_FAILED(rv))
     return rv;
 
   NS_ASSERTION(aEvent->mReply.mString.IsEmpty(),
                "The reply string must be empty");
 
@@ -420,75 +453,178 @@ nsQueryContentEventHandler::OnQueryTextC
   rv = GenerateFlatTextContent(range, aEvent->mReply.mString);
   NS_ENSURE_SUCCESS(rv, rv);
 
   aEvent->mSucceeded = PR_TRUE;
 
   return NS_OK;
 }
 
+// Adjust to use a child node if possible
+// to make the returned rect more accurate
+static nsINode* AdjustTextRectNode(nsINode* aNode,
+                                   PRInt32& aOffset)
+{
+  PRInt32 childCount = PRInt32(aNode->GetChildCount());
+  nsINode* node = aNode;
+  if (childCount) {
+    if (aOffset < childCount) {
+      node = aNode->GetChildAt(aOffset);
+      aOffset = 0;
+    } else if (aOffset == childCount) {
+      node = aNode->GetChildAt(childCount - 1);
+      aOffset = node->IsNodeOfType(nsINode::eTEXT) ?
+          static_cast<nsIContent*>(node)->TextLength() : 1;
+    }
+  }
+  return node;
+}
+
+// Similar to nsFrameSelection::GetFrameForNodeOffset,
+// but this is more flexible for OnQueryTextRect to use
+static nsresult GetFrameForTextRect(nsIPresShell* aPresShell,
+                                    nsINode* aNode,
+                                    PRInt32 aOffset,
+                                    PRBool aHint,
+                                    nsIFrame** aReturnFrame)
+{
+  NS_ENSURE_TRUE(aNode && aNode->IsNodeOfType(nsINode::eCONTENT),
+                 NS_ERROR_UNEXPECTED);
+  nsIContent* content = static_cast<nsIContent*>(aNode);
+  nsIFrame* frame = aPresShell->GetPrimaryFrameFor(content);
+  NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+  PRInt32 childOffset = 0;
+  return frame->GetChildFrameContainingOffset(aOffset, aHint, &childOffset,
+                                              aReturnFrame);
+}
+
 nsresult
-nsQueryContentEventHandler::QueryRectFor(nsQueryContentEvent* aEvent,
-                                         nsIRange* aRange,
-                                         nsCaret* aCaret)
+nsContentEventHandler::OnQueryTextRect(nsQueryContentEvent* aEvent)
 {
-  PRInt32 offsetInFrame;
-  nsIFrame* frame;
-  nsresult rv = GetStartFrameAndOffset(aRange, &frame, &offsetInFrame);
+  nsresult rv = Init(aEvent);
+  if (NS_FAILED(rv))
+    return rv;
+
+  nsRefPtr<nsRange> range = new nsRange();
+  if (!range) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset,
+                                  aEvent->mInput.mLength, PR_TRUE);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsPoint posInFrame;
-  rv = frame->GetPointFromOffset(aRange->StartOffset(), &posInFrame);
+  // used to iterate over all contents and their frames
+  nsCOMPtr<nsIContentIterator> iter;
+  rv = NS_NewContentIterator(getter_AddRefs(iter));
+  NS_ENSURE_SUCCESS(rv, rv);
+  iter->Init(range);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsRect rect;
-  rect.y = posInFrame.y;
-  rect.height = frame->GetSize().height;
+  // get the starting frame
+  PRInt32 offset = range->StartOffset();
+  nsINode* node = iter->GetCurrentNode();
+  if (!node) {
+    node = AdjustTextRectNode(range->GetStartParent(), offset);
+  }
+  nsIFrame* firstFrame = nsnull;
+  rv = GetFrameForTextRect(mPresShell, node, offset,
+                           PR_TRUE, &firstFrame);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // get the starting frame rect
+  nsRect rect(nsPoint(0, 0), firstFrame->GetRect().Size());
+  rv = ConvertToRootViewRelativeOffset(firstFrame, rect);
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsRect frameRect = rect;
+  nsPoint ptOffset;
+  firstFrame->GetPointFromOffset(offset, &ptOffset);
+  // minus 1 to avoid creating an empty rect
+  rect.x += ptOffset.x - 1;
+  rect.width -= ptOffset.x - 1;
 
-  if (aEvent->message == NS_QUERY_CHARACTER_RECT) {
-    nsPoint nextPos;
-    rv = frame->GetPointFromOffset(aRange->EndOffset(), &nextPos);
+  // get the ending frame
+  offset = range->EndOffset();
+  node = AdjustTextRectNode(range->GetEndParent(), offset);
+  nsIFrame* lastFrame = nsnull;
+  rv = GetFrameForTextRect(mPresShell, node, offset,
+                           range->Collapsed(), &lastFrame);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // iterate over all covered frames
+  for (nsIFrame* frame = firstFrame; frame != lastFrame;) {
+    frame = frame->GetNextContinuation();
+    if (!frame) {
+      do {
+        iter->Next();
+        node = iter->GetCurrentNode();
+        if (!node || !node->IsNodeOfType(nsINode::eCONTENT))
+          continue;
+        frame = mPresShell->GetPrimaryFrameFor(static_cast<nsIContent*>(node));
+      } while (!frame && !iter->IsDone());
+      if (!frame) {
+        // this can happen when the end offset of the range is 0.
+        frame = lastFrame;
+      }
+    }
+    frameRect.SetRect(nsPoint(0, 0), frame->GetRect().Size());
+    rv = ConvertToRootViewRelativeOffset(frame, frameRect);
     NS_ENSURE_SUCCESS(rv, rv);
-    rect.x = PR_MIN(posInFrame.x, nextPos.x);
-    rect.width = PR_ABS(posInFrame.x - nextPos.x);
-  } else {
-    rect.x = posInFrame.x;
-    rect.width = aCaret->GetCaretRect().width;
+    if (frame != lastFrame) {
+      // not last frame, so just add rect to previous result
+      rect.UnionRect(rect, frameRect);
+    }
   }
 
-  rv = ConvertToRootViewRelativeOffset(frame, rect);
-  NS_ENSURE_SUCCESS(rv, rv);
+  // get the ending frame rect
+  lastFrame->GetPointFromOffset(offset, &ptOffset);
+  // minus 1 to avoid creating an empty rect
+  frameRect.width -= lastFrame->GetRect().width - ptOffset.x - 1;
 
-  aEvent->mReply.mRect = nsRect::ToOutsidePixels(rect, mPresContext->AppUnitsPerDevPixel());
+  if (firstFrame == lastFrame) {
+    rect.IntersectRect(rect, frameRect);
+  } else {
+    rect.UnionRect(rect, frameRect);
+  }
+  aEvent->mReply.mRect =
+      nsRect::ToOutsidePixels(rect, mPresContext->AppUnitsPerDevPixel());
   aEvent->mSucceeded = PR_TRUE;
   return NS_OK;
 }
 
 nsresult
-nsQueryContentEventHandler::OnQueryCharacterRect(nsQueryContentEvent* aEvent)
+nsContentEventHandler::OnQueryEditorRect(nsQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
   if (NS_FAILED(rv))
     return rv;
 
-  nsCOMPtr<nsIRange> range = new nsRange();
-  NS_ENSURE_TRUE(range, NS_ERROR_OUT_OF_MEMORY);
-  rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset, 1, PR_TRUE);
+  nsIFrame* frame = mPresShell->GetPrimaryFrameFor(mRootContent);
+  NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+
+  // get rect for first frame
+  nsRect resultRect(nsPoint(0, 0), frame->GetRect().Size());
+  rv = ConvertToRootViewRelativeOffset(frame, resultRect);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (range->Collapsed()) {
-    // There is no character at the offset.
-    return NS_OK;
+  // account for any additional frames
+  while ((frame = frame->GetNextContinuation()) != nsnull) {
+    nsRect frameRect(nsPoint(0, 0), frame->GetRect().Size());
+    rv = ConvertToRootViewRelativeOffset(frame, frameRect);
+    NS_ENSURE_SUCCESS(rv, rv);
+    resultRect.UnionRect(resultRect, frameRect);
   }
 
-  return QueryRectFor(aEvent, range, nsnull);
+  aEvent->mReply.mRect =
+      nsRect::ToOutsidePixels(resultRect, mPresContext->AppUnitsPerDevPixel());
+  aEvent->mSucceeded = PR_TRUE;
+  return NS_OK;
 }
 
 nsresult
-nsQueryContentEventHandler::OnQueryCaretRect(nsQueryContentEvent* aEvent)
+nsContentEventHandler::OnQueryCaretRect(nsQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
   if (NS_FAILED(rv))
     return rv;
 
   nsRefPtr<nsCaret> caret;
   rv = mPresShell->GetCaret(getter_AddRefs(caret));
   NS_ENSURE_SUCCESS(rv, rv);
@@ -497,72 +633,104 @@ nsQueryContentEventHandler::OnQueryCaret
   // When the selection is collapsed and the queried offset is current caret
   // position, we should return the "real" caret rect.
   PRBool selectionIsCollapsed;
   rv = mSelection->GetIsCollapsed(&selectionIsCollapsed);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (selectionIsCollapsed) {
     PRUint32 offset;
-    rv = GetFlatTextOffsetOfRange(mFirstSelectedRange, &offset);
+    rv = GetFlatTextOffsetOfRange(mRootContent, mFirstSelectedRange, &offset);
     NS_ENSURE_SUCCESS(rv, rv);
     if (offset == aEvent->mInput.mOffset) {
       PRBool isCollapsed;
       nsRect rect;
       rv = caret->GetCaretCoordinates(nsCaret::eTopLevelWindowCoordinates,
                                       mSelection, &rect,
                                       &isCollapsed, nsnull);
-      aEvent->mReply.mRect = nsRect::ToOutsidePixels(rect, mPresContext->AppUnitsPerDevPixel());
+      aEvent->mReply.mRect =
+          nsRect::ToOutsidePixels(rect, mPresContext->AppUnitsPerDevPixel());
       NS_ENSURE_SUCCESS(rv, rv);
       aEvent->mSucceeded = PR_TRUE;
       return NS_OK;
     }
   }
 
   // Otherwise, we should set the guessed caret rect.
   nsCOMPtr<nsIRange> range = new nsRange();
   NS_ENSURE_TRUE(range, NS_ERROR_OUT_OF_MEMORY);
   rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset, 0, PR_TRUE);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return QueryRectFor(aEvent, range, caret);
+  PRInt32 offsetInFrame;
+  nsIFrame* frame;
+  rv = GetStartFrameAndOffset(range, &frame, &offsetInFrame);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsPoint posInFrame;
+  rv = frame->GetPointFromOffset(range->StartOffset(), &posInFrame);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRect rect;
+  rect.x = posInFrame.x;
+  rect.y = posInFrame.y;
+  rect.width = caret->GetCaretRect().width;
+  rect.height = frame->GetSize().height;
+
+  rv = ConvertToRootViewRelativeOffset(frame, rect);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  aEvent->mReply.mRect =
+      nsRect::ToOutsidePixels(rect, mPresContext->AppUnitsPerDevPixel());
+  aEvent->mSucceeded = PR_TRUE;
+  return NS_OK;
 }
 
 nsresult
-nsQueryContentEventHandler::GetFlatTextOffsetOfRange(nsIRange* aRange,
-                                                     PRUint32* aNativeOffset)
+nsContentEventHandler::GetFlatTextOffsetOfRange(nsIContent* aRootContent,
+                                                nsINode* aNode,
+                                                PRInt32 aNodeOffset,
+                                                PRUint32* aNativeOffset)
 {
   NS_ASSERTION(aNativeOffset, "param is invalid");
 
   nsCOMPtr<nsIRange> prev = new nsRange();
   NS_ENSURE_TRUE(prev, NS_ERROR_OUT_OF_MEMORY);
   nsCOMPtr<nsIDOMRange> domPrev(do_QueryInterface(prev));
   NS_ASSERTION(domPrev, "nsRange doesn't have nsIDOMRange??");
-  nsCOMPtr<nsIDOMNode> rootDOMNode(do_QueryInterface(mRootContent));
+  nsCOMPtr<nsIDOMNode> rootDOMNode(do_QueryInterface(aRootContent));
   domPrev->SetStart(rootDOMNode, 0);
 
-  nsINode* startNode = aRange->GetStartParent();
-  NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
-
-  PRInt32 startOffset = aRange->StartOffset();
-  nsCOMPtr<nsIDOMNode> startDOMNode(do_QueryInterface(startNode));
+  nsCOMPtr<nsIDOMNode> startDOMNode(do_QueryInterface(aNode));
   NS_ASSERTION(startDOMNode, "startNode doesn't have nsIDOMNode");
-  domPrev->SetEnd(startDOMNode, startOffset);
+  domPrev->SetEnd(startDOMNode, aNodeOffset);
 
   nsAutoString prevStr;
   nsresult rv = GenerateFlatTextContent(prev, prevStr);
   NS_ENSURE_SUCCESS(rv, rv);
   *aNativeOffset = prevStr.Length();
   return NS_OK;
 }
 
 nsresult
-nsQueryContentEventHandler::GetStartFrameAndOffset(nsIRange* aRange,
-                                                   nsIFrame** aFrame,
-                                                   PRInt32* aOffsetInFrame)
+nsContentEventHandler::GetFlatTextOffsetOfRange(nsIContent* aRootContent,
+                                                nsIRange* aRange,
+                                                PRUint32* aNativeOffset)
+{
+  nsINode* startNode = aRange->GetStartParent();
+  NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
+  PRInt32 startOffset = aRange->StartOffset();
+  return GetFlatTextOffsetOfRange(aRootContent, startNode, startOffset,
+                                  aNativeOffset);
+}
+
+nsresult
+nsContentEventHandler::GetStartFrameAndOffset(nsIRange* aRange,
+                                              nsIFrame** aFrame,
+                                              PRInt32* aOffsetInFrame)
 {
   NS_ASSERTION(aRange && aFrame && aOffsetInFrame, "params are invalid");
 
   nsIContent* content = nsnull;
   nsINode* node = aRange->GetStartParent();
   if (node && node->IsNodeOfType(nsINode::eCONTENT))
     content = static_cast<nsIContent*>(node);
   NS_ASSERTION(content, "the start node doesn't have nsIContent!");
@@ -572,21 +740,104 @@ nsQueryContentEventHandler::GetStartFram
                                       fs->GetHint(), aOffsetInFrame);
   NS_ENSURE_TRUE((*aFrame), NS_ERROR_FAILURE);
   NS_ASSERTION((*aFrame)->GetType() == nsGkAtoms::textFrame,
                "The frame is not textframe");
   return NS_OK;
 }
 
 nsresult
-nsQueryContentEventHandler::ConvertToRootViewRelativeOffset(nsIFrame* aFrame,
-                                                            nsRect& aRect)
+nsContentEventHandler::ConvertToRootViewRelativeOffset(nsIFrame* aFrame,
+                                                       nsRect& aRect)
 {
   NS_ASSERTION(aFrame, "aFrame must not be null");
 
   nsIView* view = nsnull;
   nsPoint posInView;
   aFrame->GetOffsetFromView(posInView, &view);
   if (!view)
     return NS_ERROR_FAILURE;
   aRect += posInView + view->GetOffsetTo(nsnull);
   return NS_OK;
 }
+
+static void AdjustRangeForSelection(nsIContent* aRoot,
+                                    nsINode** aNode,
+                                    PRInt32* aOffset)
+{
+  nsINode* node = *aNode;
+  PRInt32 offset = *aOffset;
+  if (aRoot != node && node->GetParent() &&
+      !node->IsNodeOfType(nsINode::eTEXT)) {
+    node = node->GetParent();
+    offset = node->IndexOf(*aNode) + (offset ? 1 : 0);
+  }
+  nsINode* brNode = node->GetChildAt(offset - 1);
+  while (brNode && brNode->IsNodeOfType(nsINode::eHTML)) {
+    nsIContent* brContent = static_cast<nsIContent*>(brNode);
+    if (brContent->Tag() != nsGkAtoms::br || IsContentBR(brContent))
+      break;
+    brNode = node->GetChildAt(--offset - 1);
+  }
+  *aNode = node;
+  *aOffset = PR_MAX(offset, 0);
+}
+
+nsresult
+nsContentEventHandler::OnSelectionEvent(nsSelectionEvent* aEvent)
+{
+  aEvent->mSucceeded = PR_FALSE;
+
+  // Get selection to manipulate
+  nsCOMPtr<nsISelection> sel;
+  nsresult rv = nsIMEStateManager::
+      GetFocusSelectionAndRoot(getter_AddRefs(sel),
+                               getter_AddRefs(mRootContent));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Get range from offset and length
+  nsRefPtr<nsRange> range = new nsRange();
+  NS_ENSURE_TRUE(range, NS_ERROR_OUT_OF_MEMORY);
+  rv = SetRangeFromFlatTextOffset(range, aEvent->mOffset,
+                                  aEvent->mLength, PR_TRUE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsINode* startNode = range->GetStartParent();
+  nsINode* endNode = range->GetEndParent();
+  PRInt32 startOffset = range->StartOffset();
+  PRInt32 endOffset = range->EndOffset();
+  AdjustRangeForSelection(mRootContent, &startNode, &startOffset);
+  AdjustRangeForSelection(mRootContent, &endNode, &endOffset);
+
+  nsCOMPtr<nsIDOMNode> startDomNode(do_QueryInterface(startNode));
+  nsCOMPtr<nsIDOMNode> endDomNode(do_QueryInterface(endNode));
+  NS_ENSURE_TRUE(startDomNode && endDomNode, NS_ERROR_UNEXPECTED);
+
+  nsCOMPtr<nsISelectionPrivate> selPrivate = do_QueryInterface(sel);
+  NS_ENSURE_TRUE(selPrivate, NS_ERROR_UNEXPECTED);
+  selPrivate->StartBatchChanges();
+
+  // Clear selection first before setting
+  rv = sel->RemoveAllRanges();
+  // Need to call EndBatchChanges at the end even if call failed
+  if (NS_SUCCEEDED(rv)) {
+    if (aEvent->mReversed) {
+      rv = sel->Collapse(endDomNode, endOffset);
+    } else {
+      rv = sel->Collapse(startDomNode, startOffset);
+    }
+    if (NS_SUCCEEDED(rv) &&
+        (startDomNode != endDomNode || startOffset != endOffset)) {
+      if (aEvent->mReversed) {
+        rv = sel->Extend(startDomNode, startOffset);
+      } else {
+        rv = sel->Extend(endDomNode, endOffset);
+      }
+    }
+  }
+  selPrivate->EndBatchChanges();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsISelection2>(do_QueryInterface(sel))->ScrollIntoView(
+      nsISelectionController::SELECTION_FOCUS_REGION, PR_FALSE, -1, -1);
+  aEvent->mSucceeded = PR_TRUE;
+  return NS_OK;
+}
rename from content/events/src/nsQueryContentEventHandler.h
rename to content/events/src/nsContentEventHandler.h
--- a/content/events/src/nsQueryContentEventHandler.h
+++ b/content/events/src/nsContentEventHandler.h
@@ -16,100 +16,112 @@
  *
  * The Initial Developer of the Original Code is
  * Mozilla Japan.
  * Portions created by the Initial Developer are Copyright (C) 2008
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Masayuki Nakano <masayuki@d-toybox.com>
+ *   Ningjie Chen <chenn@email.uc.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either of the GNU General Public License Version 2 or later (the "GPL"),
  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-#ifndef nsQueryContentEventHandler_h__
-#define nsQueryContentEventHandler_h__
+#ifndef nsContentEventHandler_h__
+#define nsContentEventHandler_h__
 
 #include "nscore.h"
 #include "nsCOMPtr.h"
 
 #include "nsISelection.h"
 #include "nsIRange.h"
 #include "nsIContent.h"
 #include "nsIDOMTreeWalker.h"
 
 class nsPresContext;
 class nsIPresShell;
 class nsQueryContentEvent;
+class nsSelectionEvent;
 class nsCaret;
 struct nsRect;
 
 /*
  * Query Content Event Handler
- *   nsQueryContentEventHandler is a helper class for nsEventStateManager.
+ *   nsContentEventHandler is a helper class for nsEventStateManager.
  *   The platforms request some content informations, e.g., the selected text,
  *   the offset of the selected text and the text for specified range.
  *   This class answers to NS_QUERY_* events from actual contents.
  */
 
-class NS_STACK_CLASS nsQueryContentEventHandler {
+class NS_STACK_CLASS nsContentEventHandler {
 public:
-  nsQueryContentEventHandler(nsPresContext *aPresContext);
+  nsContentEventHandler(nsPresContext *aPresContext);
 
   // NS_QUERY_SELECTED_TEXT event handler
   nsresult OnQuerySelectedText(nsQueryContentEvent* aEvent);
   // NS_QUERY_TEXT_CONTENT event handler
   nsresult OnQueryTextContent(nsQueryContentEvent* aEvent);
-  // NS_QUERY_CHARACTER_RECT event handler
-  nsresult OnQueryCharacterRect(nsQueryContentEvent* aEvent);
   // NS_QUERY_CARET_RECT event handler
   nsresult OnQueryCaretRect(nsQueryContentEvent* aEvent);
+  // NS_QUERY_TEXT_RECT event handler
+  nsresult OnQueryTextRect(nsQueryContentEvent* aEvent);
+  // NS_QUERY_EDITOR_RECT event handler
+  nsresult OnQueryEditorRect(nsQueryContentEvent* aEvent);
+
+  // NS_SELECTION_* event
+  nsresult OnSelectionEvent(nsSelectionEvent* aEvent);
+
 protected:
   nsPresContext* mPresContext;
   nsIPresShell* mPresShell;
   nsCOMPtr<nsISelection> mSelection;
   nsCOMPtr<nsIRange> mFirstSelectedRange;
   nsCOMPtr<nsIContent> mRootContent;
 
   nsresult Init(nsQueryContentEvent* aEvent);
 
+public:
   // FlatText means the text that is generated from DOM tree. The BR elements
   // are replaced to native linefeeds. Other elements are ignored.
 
-  // Generate the FlatText from DOM range.
-  nsresult GenerateFlatTextContent(nsIRange* aRange, nsAFlatString& aString);
+  // Get the offset in FlatText of the range. (also used by nsIMEStateManager)
+  static nsresult GetFlatTextOffsetOfRange(nsIContent* aRootContent,
+                                           nsINode* aNode,
+                                           PRInt32 aNodeOffset,
+                                           PRUint32* aOffset);
+  static nsresult GetFlatTextOffsetOfRange(nsIContent* aRootContent,
+                                           nsIRange* aRange,
+                                           PRUint32* aOffset);
+protected:
   // Make the DOM range from the offset of FlatText and the text length.
   // If aExpandToClusterBoundaries is true, the start offset and the end one are
   // expanded to nearest cluster boundaries.
   nsresult SetRangeFromFlatTextOffset(nsIRange* aRange,
                                       PRUint32 aNativeOffset,
                                       PRUint32 aNativeLength,
                                       PRBool aExpandToClusterBoundaries);
-  // Get the offset in FlatText of the range.
-  nsresult GetFlatTextOffsetOfRange(nsIRange* aRange, PRUint32* aOffset);
   // Find the first textframe for the range, and get the start offset in
   // the frame.
   nsresult GetStartFrameAndOffset(nsIRange* aRange,
-                                  nsIFrame** aFrame, PRInt32* aOffsetInFrame);
+                                  nsIFrame** aFrame,
+                                  PRInt32* aOffsetInFrame);
   // Convert the frame relative offset to the root view relative offset.
-  nsresult ConvertToRootViewRelativeOffset(nsIFrame* aFrame, nsRect& aRect);
-  // The helper for OnQueryCharacterRect/OnQueryCaretRect.
-  // Don't call for another event.
-  nsresult QueryRectFor(nsQueryContentEvent* aEvent, nsIRange* aRange,
-                        nsCaret* aCaret);
+  nsresult ConvertToRootViewRelativeOffset(nsIFrame* aFrame,
+                                           nsRect& aRect);
   // Expand aXPOffset to the nearest offset in cluster boundary. aForward is
   // true, it is expanded to forward.
   nsresult ExpandToClusterBoundary(nsIContent* aContent, PRBool aForward,
                                    PRUint32* aXPOffset);
 };
 
-#endif // nsQueryContentEventHandler_h__
+#endif // nsContentEventHandler_h__
--- a/content/events/src/nsEventStateManager.cpp
+++ b/content/events/src/nsEventStateManager.cpp
@@ -23,16 +23,17 @@
  * Contributor(s):
  *   Makoto Kato  <m_kato@ga2.so-net.ne.jp>
  *   Dean Tessman <dean_tessman@hotmail.com>
  *   Mats Palmgren <mats.palmgren@bredband.net>
  *   Masayuki Nakano <masayuki@d-toybox.com>
  *   Ginn Chen <ginn.chen@sun.com>
  *   Simon B├╝nzli <zeniko@gmail.com>
  *   Ehsan Akhgari <ehsan.akhgari@gmail.com>
+ *   Ningjie Chen <chenn@email.uc.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either of the GNU General Public License Version 2 or later (the "GPL"),
  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -42,17 +43,17 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsCOMPtr.h"
 #include "nsEventStateManager.h"
 #include "nsEventListenerManager.h"
 #include "nsIMEStateManager.h"
-#include "nsQueryContentEventHandler.h"
+#include "nsContentEventHandler.h"
 #include "nsIContent.h"
 #include "nsINodeInfo.h"
 #include "nsIDocument.h"
 #include "nsIFrame.h"
 #include "nsIWidget.h"
 #include "nsPresContext.h"
 #include "nsIPresShell.h"
 #include "nsDOMEvent.h"
@@ -954,16 +955,18 @@ nsEventStateManager::PreHandleEvent(nsPr
       // If the document didn't change, then the only thing that could have
       // changed is the focused content node.  That's handled elsewhere
       // (SetContentState and SendFocusBlur).
 
       if (gLastFocusedDocument == mDocument)
         break;
 
       if (mDocument) {
+        nsIMEStateManager::OnTextStateBlur(mPresContext, mCurrentFocus);
+
         if (gLastFocusedDocument && gLastFocusedPresContextWeak) {
           nsCOMPtr<nsPIDOMWindow> ourWindow =
             gLastFocusedDocument->GetWindow();
 
           // If the focus controller is already suppressed, it means that we
           // are in the middle of an activate sequence. In this case, we do
           // _not_ want to fire a blur on the previously focused content, since
           // we will be focusing it again later when we receive the NS_ACTIVATE
@@ -1071,16 +1074,18 @@ nsEventStateManager::PreHandleEvent(nsPr
           focusevent.target = nsnull;
           nsEventDispatcher::Dispatch(window, aPresContext, &focusevent,
                                       nsnull, &status);
 
           SetFocusedContent(currentFocus); // we kept this reference above
           NS_IF_RELEASE(gLastFocusedContent);
           gLastFocusedContent = mCurrentFocus;
           NS_IF_ADDREF(gLastFocusedContent);
+
+          nsIMEStateManager::OnTextStateFocus(mPresContext, mCurrentFocus);
         }
 
         // Try to keep the focus controllers and the globals in synch
         if (gLastFocusedDocument && gLastFocusedDocument != mDocument) {
 
           nsIFocusController *lastController = nsnull;
           nsPIDOMWindow* lastWindow = gLastFocusedDocument->GetWindow();
           if (lastWindow)
@@ -1142,16 +1147,18 @@ nsEventStateManager::PreHandleEvent(nsPr
         // immediately.
 
         EnsureDocument(aPresContext);
 
         if (gLastFocusedContent && !gLastFocusedContent->IsInDoc()) {
           NS_RELEASE(gLastFocusedContent);
         }
 
+        nsIMEStateManager::OnTextStateBlur(nsnull, nsnull);
+
         // Now fire blurs.  We fire a blur on the focused document, element,
         // and window.
 
         nsEventStatus status = nsEventStatus_eIgnore;
         nsEvent event(PR_TRUE, NS_BLUR_CONTENT);
         event.flags |= NS_EVENT_FLAG_CANT_BUBBLE;
 
         if (gLastFocusedDocument && gLastFocusedPresContextWeak) {
@@ -1315,16 +1322,18 @@ nsEventStateManager::PreHandleEvent(nsPr
       // window.
 
       nsCOMPtr<nsIFocusController> focusController =
         GetFocusControllerForDocument(mDocument);
 
       if (focusController)
         focusController->SetSuppressFocus(PR_TRUE, "Deactivate Suppression");
 
+      nsIMEStateManager::OnTextStateBlur(nsnull, nsnull);
+
       // Now fire blurs.  Blur the content, then the document, then the window.
 
       if (gLastFocusedDocument && gLastFocusedDocument == mDocument &&
           gLastFocusedDocument != mFirstDocumentBlurEvent) {
 
         PRBool clearFirstDocumentBlurEvent = PR_FALSE;
         if (!mFirstDocumentBlurEvent) {
           mFirstDocumentBlurEvent = gLastFocusedDocument;
@@ -1489,38 +1498,50 @@ nsEventStateManager::PreHandleEvent(nsPr
       if ((msEvent->scrollFlags & nsMouseScrollEvent::kIsHorizontal) ?
            mLastLineScrollConsumedX : mLastLineScrollConsumedY) {
         *aStatus = nsEventStatus_eConsumeNoDefault;
       }
     }
     break;
   case NS_QUERY_SELECTED_TEXT:
     {
-      nsQueryContentEventHandler handler(mPresContext);
+      nsContentEventHandler handler(mPresContext);
       handler.OnQuerySelectedText((nsQueryContentEvent*)aEvent);
     }
     break;
   case NS_QUERY_TEXT_CONTENT:
     {
-      nsQueryContentEventHandler handler(mPresContext);
+      nsContentEventHandler handler(mPresContext);
       handler.OnQueryTextContent((nsQueryContentEvent*)aEvent);
     }
     break;
-  case NS_QUERY_CHARACTER_RECT:
-    {
-      nsQueryContentEventHandler handler(mPresContext);
-      handler.OnQueryCharacterRect((nsQueryContentEvent*)aEvent);
-    }
-    break;
   case NS_QUERY_CARET_RECT:
     {
-      nsQueryContentEventHandler handler(mPresContext);
+      nsContentEventHandler handler(mPresContext);
       handler.OnQueryCaretRect((nsQueryContentEvent*)aEvent);
     }
     break;
+  case NS_QUERY_TEXT_RECT:
+    {
+      nsContentEventHandler handler(mPresContext);
+      handler.OnQueryTextRect((nsQueryContentEvent*)aEvent);
+    }
+    break;
+  case NS_QUERY_EDITOR_RECT:
+    {
+      nsContentEventHandler handler(mPresContext);
+      handler.OnQueryEditorRect((nsQueryContentEvent*)aEvent);
+    }
+    break;
+  case NS_SELECTION_SET:
+    {
+      nsContentEventHandler handler(mPresContext);
+      handler.OnSelectionEvent((nsSelectionEvent*)aEvent);
+    }
+    break;
   }
   return NS_OK;
 }
 
 static PRInt32
 GetAccessModifierMask(nsISupports* aDocShell)
 {
   nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(aDocShell));
@@ -5026,16 +5047,18 @@ nsEventStateManager::SendFocusBlur(nsPre
   // send it a blur.
 
   if (previousFocus && !previousFocus->GetDocument())
     previousFocus = nsnull;
 
   // Track the old focus controller if any focus suppressions is used on it.
   nsFocusSuppressor oldFocusSuppressor;
   
+  nsIMEStateManager::OnTextStateBlur(aPresContext, aContent);
+
   if (nsnull != gLastFocusedPresContextWeak) {
 
     nsCOMPtr<nsIContent> focusAfterBlur;
 
     if (gLastFocusedContent && gLastFocusedContent != mFirstBlurEvent) {
 
       //Store the first blur event we fire and don't refire blur
       //to that element while the first blur is still ongoing.
@@ -5256,16 +5279,18 @@ nsEventStateManager::SendFocusBlur(nsPre
     PRInt32 ec, val = tabIndex.ToInteger(&ec);
     if (NS_SUCCEEDED (ec)) {
       mCurrentTabIndex = val;
     }
 
     if (clearFirstFocusEvent) {
       mFirstFocusEvent = nsnull;
     }
+
+    nsIMEStateManager::OnTextStateFocus(mPresContext, mCurrentFocus);
   } else if (!aContent) {
     //fire focus on document even if the content isn't focusable (ie. text)
     //see bugzilla bug 93521
     nsEventStatus status = nsEventStatus_eIgnore;
     nsEvent event(PR_TRUE, NS_FOCUS_CONTENT);
     event.flags |= NS_EVENT_FLAG_CANT_BUBBLE;
 
     if (nsnull != mPresContext && mDocument) {
--- a/content/events/src/nsIMEStateManager.cpp
+++ b/content/events/src/nsIMEStateManager.cpp
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is
  * Mozilla Japan.
  * Portions created by the Initial Developer are Copyright (C) 2006
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Masayuki Nakano <masayuki@d-toybox.com>
+ *   Ningjie Chen <chenn@email.uc.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either of the GNU General Public License Version 2 or later (the "GPL"),
  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -48,34 +49,47 @@
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIEditorDocShell.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
 #include "nsPresContext.h"
 #include "nsIFocusController.h"
 #include "nsIDOMWindow.h"
 #include "nsContentUtils.h"
+#include "nsINode.h"
+#include "nsIFrame.h"
+#include "nsRange.h"
+#include "nsIDOMRange.h"
+#include "nsISelection.h"
+#include "nsISelectionPrivate.h"
+#include "nsISelectionListener.h"
+#include "nsISelectionController.h"
+#include "nsIMutationObserver.h"
+#include "nsContentEventHandler.h"
 
 /******************************************************************/
 /* nsIMEStateManager                                              */
 /******************************************************************/
 
 nsIContent*    nsIMEStateManager::sContent      = nsnull;
 nsPresContext* nsIMEStateManager::sPresContext  = nsnull;
 nsPIDOMWindow* nsIMEStateManager::sActiveWindow = nsnull;
 PRBool         nsIMEStateManager::sInstalledMenuKeyboardListener = PR_FALSE;
 
+nsTextStateManager* nsIMEStateManager::sTextStateObserver = nsnull;
+
 nsresult
 nsIMEStateManager::OnDestroyPresContext(nsPresContext* aPresContext)
 {
   NS_ENSURE_ARG_POINTER(aPresContext);
   if (aPresContext != sPresContext)
     return NS_OK;
   sContent = nsnull;
   sPresContext = nsnull;
+  OnTextStateBlur(nsnull, nsnull);
   return NS_OK;
 }
 
 nsresult
 nsIMEStateManager::OnRemoveContent(nsPresContext* aPresContext,
                                    nsIContent* aContent)
 {
   NS_ENSURE_ARG_POINTER(aPresContext);
@@ -264,8 +278,306 @@ nsIMEStateManager::GetWidget(nsPresConte
   if (!vm)
     return nsnull;
   nsCOMPtr<nsIWidget> widget = nsnull;
   nsresult rv = vm->GetWidget(getter_AddRefs(widget));
   NS_ENSURE_SUCCESS(rv, nsnull);
   return widget;
 }
 
+
+// nsTextStateManager notifies widget of any text and selection changes
+//  in the currently focused editor
+// sTextStateObserver points to the currently active nsTextStateManager
+// sTextStateObserver is null if there is no focused editor
+
+class nsTextStateManager : public nsISelectionListener,
+                           public nsStubMutationObserver
+{
+public:
+  nsTextStateManager();
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISELECTIONLISTENER
+  NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+  nsresult Init(nsIWidget* aWidget,
+                nsPresContext* aPresContext,
+                nsINode* aNode);
+  void     Destroy(void);
+
+  nsCOMPtr<nsIWidget>            mWidget;
+  nsCOMPtr<nsISelection>         mSel;
+  nsCOMPtr<nsIContent>           mRootContent;
+  nsCOMPtr<nsINode>              mEditableNode;
+  PRBool                         mDestroying;
+
+private:
+  void NotifyContentAdded(nsINode* aContainer, PRInt32 aStart, PRInt32 aEnd);
+};
+
+nsTextStateManager::nsTextStateManager()
+{
+  mDestroying = PR_FALSE;
+}
+
+nsresult
+nsTextStateManager::Init(nsIWidget* aWidget,
+                         nsPresContext* aPresContext,
+                         nsINode* aNode)
+{
+  mWidget = aWidget;
+
+  nsIPresShell* presShell = aPresContext->PresShell();
+
+  // get selection and root content
+  nsCOMPtr<nsISelectionController> selCon;
+  if (aNode->IsNodeOfType(nsINode::eCONTENT)) {
+    nsIFrame* frame = presShell->GetPrimaryFrameFor(
+                                     static_cast<nsIContent*>(aNode));
+    NS_ENSURE_TRUE(frame, NS_ERROR_UNEXPECTED);
+
+    frame->GetSelectionController(aPresContext,
+                                  getter_AddRefs(selCon));
+  } else {
+    // aNode is a document
+    selCon = do_QueryInterface(presShell);
+  }
+  NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
+
+  nsCOMPtr<nsISelection> sel;
+  nsresult rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
+                                     getter_AddRefs(sel));
+  NS_ENSURE_TRUE(sel, NS_ERROR_UNEXPECTED);
+
+  nsCOMPtr<nsIDOMRange> selDomRange;
+  rv = sel->GetRangeAt(0, getter_AddRefs(selDomRange));
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIRange> selRange(do_QueryInterface(selDomRange));
+  NS_ENSURE_TRUE(selRange && selRange->GetStartParent(), NS_ERROR_UNEXPECTED);
+
+  mRootContent = selRange->GetStartParent()->
+                     GetSelectionRootContent(presShell);
+
+  // add text change observer
+  mRootContent->AddMutationObserver(this);
+
+  // add selection change listener
+  nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(sel));
+  NS_ENSURE_TRUE(selPrivate, NS_ERROR_UNEXPECTED);
+  rv = selPrivate->AddSelectionListener(this);
+  NS_ENSURE_SUCCESS(rv, rv);
+  mSel = sel;
+
+  mEditableNode = aNode;
+  return NS_OK;
+}
+
+void
+nsTextStateManager::Destroy(void)
+{
+  if (mSel) {
+    nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSel));
+    if (selPrivate)
+      selPrivate->RemoveSelectionListener(this);
+    mSel = nsnull;
+  }
+  if (mRootContent) {
+    mRootContent->RemoveMutationObserver(this);
+    mRootContent = nsnull;
+  }
+  mEditableNode = nsnull;
+  mWidget = nsnull;
+}
+
+NS_IMPL_ISUPPORTS2(nsTextStateManager,
+                   nsIMutationObserver,
+                   nsISelectionListener)
+
+nsresult
+nsTextStateManager::NotifySelectionChanged(nsIDOMDocument* aDoc,
+                                           nsISelection* aSel,
+                                           PRInt16 aReason)
+{
+  PRInt32 count = 0;
+  nsresult rv = aSel->GetRangeCount(&count);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (count > 0) {
+    mWidget->OnIMESelectionChange();
+  }
+  return NS_OK;
+}
+
+void
+nsTextStateManager::CharacterDataChanged(nsIDocument* aDocument,
+                                         nsIContent* aContent,
+                                         CharacterDataChangeInfo* aInfo)
+{
+  NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT),
+               "character data changed for non-text node");
+
+  PRUint32 offset = 0;
+  // get offsets of change and fire notification
+  if (NS_FAILED(nsContentEventHandler::GetFlatTextOffsetOfRange(
+                    mRootContent, aContent, aInfo->mChangeStart, &offset)))
+    return;
+
+  PRUint32 oldEnd = offset + aInfo->mChangeEnd - aInfo->mChangeStart;
+  PRUint32 newEnd = offset + aInfo->mReplaceLength;
+  mWidget->OnIMETextChange(offset, oldEnd, newEnd);
+}
+
+void
+nsTextStateManager::NotifyContentAdded(nsINode* aContainer,
+                                       PRInt32 aStartIndex,
+                                       PRInt32 aEndIndex)
+{
+  PRUint32 offset = 0, newOffset = 0;
+  if (NS_FAILED(nsContentEventHandler::GetFlatTextOffsetOfRange(
+                    mRootContent, aContainer, aStartIndex, &offset)))
+    return;
+
+  // get offset at the end of the last added node
+  if (NS_FAILED(nsContentEventHandler::GetFlatTextOffsetOfRange(
+                    aContainer->GetChildAt(aStartIndex),
+                    aContainer, aEndIndex, &newOffset)))
+    return;
+
+  // fire notification
+  if (newOffset)
+    mWidget->OnIMETextChange(offset, offset, offset + newOffset);
+}
+
+void
+nsTextStateManager::ContentAppended(nsIDocument* aDocument,
+                                    nsIContent* aContainer,
+                                    PRInt32 aNewIndexInContainer)
+{
+  NotifyContentAdded(aContainer, aNewIndexInContainer,
+                     aContainer->GetChildCount());
+}
+
+void
+nsTextStateManager::ContentInserted(nsIDocument* aDocument,
+                                     nsIContent* aContainer,
+                                     nsIContent* aChild,
+                                     PRInt32 aIndexInContainer)
+{
+  NotifyContentAdded(NODE_FROM(aContainer, aDocument),
+                     aIndexInContainer, aIndexInContainer + 1);
+}
+
+void
+nsTextStateManager::ContentRemoved(nsIDocument* aDocument,
+                                    nsIContent* aContainer,
+                                    nsIContent* aChild,
+                                    PRInt32 aIndexInContainer)
+{
+  PRUint32 offset = 0, childOffset = 1;
+  if (NS_FAILED(nsContentEventHandler::GetFlatTextOffsetOfRange(
+                    mRootContent, NODE_FROM(aContainer, aDocument),
+                    aIndexInContainer, &offset)))
+    return;
+
+  // get offset at the end of the deleted node
+  if (aChild->IsNodeOfType(nsINode::eTEXT))
+    childOffset = aChild->TextLength();
+  else if (0 < aChild->GetChildCount())
+    childOffset = aChild->GetChildCount();
+
+  if (NS_FAILED(nsContentEventHandler::GetFlatTextOffsetOfRange(
+                    aChild, aChild, childOffset, &childOffset)))
+    return;
+
+  // fire notification
+  if (childOffset)
+    mWidget->OnIMETextChange(offset, offset + childOffset, offset);
+}
+
+static nsINode* GetRootEditableNode(nsPresContext* aPresContext,
+                                    nsIContent* aContent)
+{
+  if (aContent) {
+    nsINode* root = nsnull;
+    nsINode* node = aContent;
+    while (node && node->IsEditable()) {
+      root = node;
+      node = node->GetParent();
+    }
+    return root;
+  }
+  if (aPresContext) {
+    nsIDocument* document = aPresContext->Document();
+    if (document && document->IsEditable())
+      return document;
+  }
+  return nsnull;
+}
+
+nsresult
+nsIMEStateManager::OnTextStateBlur(nsPresContext* aPresContext,
+                                   nsIContent* aContent)
+{
+  if (!sTextStateObserver || sTextStateObserver->mDestroying ||
+      sTextStateObserver->mEditableNode ==
+          GetRootEditableNode(aPresContext, aContent))
+    return NS_OK;
+
+  sTextStateObserver->mDestroying = PR_TRUE;
+  sTextStateObserver->mWidget->OnIMEFocusChange(PR_FALSE);
+  sTextStateObserver->Destroy();
+  NS_RELEASE(sTextStateObserver);
+  return NS_OK;
+}
+
+nsresult
+nsIMEStateManager::OnTextStateFocus(nsPresContext* aPresContext,
+                                    nsIContent* aContent)
+{
+  if (sTextStateObserver) return NS_OK;
+
+  nsINode *editableNode = GetRootEditableNode(aPresContext, aContent);
+  if (!editableNode) return NS_OK;
+
+  nsIViewManager* vm = aPresContext->GetViewManager();
+  NS_ENSURE_TRUE(vm, NS_ERROR_NOT_AVAILABLE);
+
+  nsCOMPtr<nsIWidget> widget;
+  nsresult rv = vm->GetWidget(getter_AddRefs(widget));
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_AVAILABLE);
+
+  rv = widget->OnIMEFocusChange(PR_TRUE);
+  NS_ENSURE_SUCCESS(rv, NS_OK);
+
+  // OnIMEFocusChange may cause focus and sTextStateObserver to change
+  // In that case return and keep the current sTextStateObserver
+  NS_ENSURE_TRUE(!sTextStateObserver, NS_OK);
+
+  sTextStateObserver = new nsTextStateManager();
+  NS_ENSURE_TRUE(sTextStateObserver, NS_ERROR_OUT_OF_MEMORY);
+  NS_ADDREF(sTextStateObserver);
+  rv = sTextStateObserver->Init(widget, aPresContext, editableNode);
+  if (NS_FAILED(rv)) {
+    sTextStateObserver->mDestroying = PR_TRUE;
+    sTextStateObserver->Destroy();
+    NS_RELEASE(sTextStateObserver);
+    widget->OnIMEFocusChange(PR_FALSE);
+    return rv;
+  }
+  return NS_OK;
+}
+
+nsresult
+nsIMEStateManager::GetFocusSelectionAndRoot(nsISelection** aSel,
+                                            nsIContent** aRoot)
+{
+  if (!sTextStateObserver || !sTextStateObserver->mEditableNode)
+    return NS_ERROR_NOT_AVAILABLE;
+
+  NS_ASSERTION(sTextStateObserver->mSel && sTextStateObserver->mRootContent,
+               "uninitialized text state observer");
+  NS_ADDREF(*aSel = sTextStateObserver->mSel);
+  NS_ADDREF(*aRoot = sTextStateObserver->mRootContent);
+  return NS_OK;
+}
--- a/content/events/src/nsIMEStateManager.h
+++ b/content/events/src/nsIMEStateManager.h
@@ -35,49 +35,73 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsIMEStateManager_h__
 #define nsIMEStateManager_h__
 
 #include "nscore.h"
+#include "nsGUIEvent.h"
 
 class nsIContent;
 class nsPIDOMWindow;
 class nsPresContext;
 class nsIWidget;
 class nsIFocusController;
+class nsTextStateManager;
+class nsISelection;
 
 /*
  * IME state manager
  */
 
 class nsIMEStateManager
 {
 public:
   static nsresult OnDestroyPresContext(nsPresContext* aPresContext);
   static nsresult OnRemoveContent(nsPresContext* aPresContext,
                                   nsIContent* aContent);
   static nsresult OnChangeFocus(nsPresContext* aPresContext,
                                 nsIContent* aContent);
   static nsresult OnActivate(nsPresContext* aPresContext);
   static nsresult OnDeactivate(nsPresContext* aPresContext);
   static void OnInstalledMenuKeyboardListener(PRBool aInstalling);
+
+  // These two methods manage focus and selection/text observers.
+  // They are separate from OnChangeFocus above because this offers finer
+  // control compared to having the two methods incorporated into OnChangeFocus
+
+  // OnTextStateBlur should be called *before* NS_BLUR_CONTENT fires
+  // aPresContext is the nsPresContext receiving focus (not lost focus)
+  // aContent is the nsIContent receiving focus (not lost focus)
+  // aPresContext and/or aContent may be null
+  static nsresult OnTextStateBlur(nsPresContext* aPresContext,
+                                  nsIContent* aContent);
+  // OnTextStateFocus should be called *after* NS_FOCUS_CONTENT fires
+  // aPresContext is the nsPresContext receiving focus
+  // aContent is the nsIContent receiving focus
+  static nsresult OnTextStateFocus(nsPresContext* aPresContext,
+                                   nsIContent* aContent);
+  // Get the focused editor's selection and root
+  static nsresult GetFocusSelectionAndRoot(nsISelection** aSel,
+                                           nsIContent** aRoot);
 protected:
   static void SetIMEState(nsPresContext* aPresContext,
                           PRUint32 aState,
                           nsIWidget* aKB);
   static PRUint32 GetNewIMEState(nsPresContext* aPresContext,
                                  nsIContent* aContent);
 
   static PRBool IsActive(nsPresContext* aPresContext);
 
   static nsIFocusController* GetFocusController(nsPresContext* aPresContext);
   static nsIWidget* GetWidget(nsPresContext* aPresContext);
 
   static nsIContent*    sContent;
   static nsPresContext* sPresContext;
   static nsPIDOMWindow* sActiveWindow;
   static PRBool         sInstalledMenuKeyboardListener;
+
+  static nsTextStateManager* sTextStateObserver;
 };
 
 #endif // nsIMEStateManager_h__
--- a/content/html/content/src/nsGenericHTMLElement.h
+++ b/content/html/content/src/nsGenericHTMLElement.h
@@ -564,16 +564,23 @@ public:
   static nsresult GetSearchFromHrefString(const nsAString &aHref,
                                           nsAString& aSearch);
 
   static nsresult GetPortFromHrefString(const nsAString &aHref,
                                         nsAString& aPort);
 
   static nsresult GetHashFromHrefString(const nsAString &aHref,
                                         nsAString& aHash);
+
+  /**
+   * Locate an nsIEditor rooted at this content node, if there is one.
+   */
+  NS_HIDDEN_(nsresult) GetEditor(nsIEditor** aEditor);
+  NS_HIDDEN_(nsresult) GetEditorInternal(nsIEditor** aEditor);
+
 protected:
   /**
    * Focus or blur the element.  This is what you should call if you want to
    * *cause* a focus or blur on your element.  SetFocus / SetBlur are the
    * methods where you want to catch what occurs on your element.
    * @param aDoFocus true to focus, false to blur
    */
   void SetElementFocus(PRBool aDoFocus);
@@ -724,22 +731,16 @@ protected:
    * attributes in null namespace.
    *
    * @param aAttr    name of attribute.
    * @param aResult  result value [out]
    */
   NS_HIDDEN_(nsresult) GetURIListAttr(nsIAtom* aAttr, nsAString& aResult);
 
   /**
-   * Locate an nsIEditor rooted at this content node, if there is one.
-   */
-  NS_HIDDEN_(nsresult) GetEditor(nsIEditor** aEditor);
-  NS_HIDDEN_(nsresult) GetEditorInternal(nsIEditor** aEditor);
-
-  /**
    * Locates the nsIEditor associated with this node.  In general this is
    * equivalent to GetEditorInternal(), but for designmode or contenteditable,
    * this may need to get an editor that's not actually on this element's
    * associated TextControlFrame.  This is used by the spellchecking routines
    * to get the editor affected by changing the spellcheck attribute on this
    * node.
    */
   virtual already_AddRefed<nsIEditor> GetAssociatedEditor();
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -1537,16 +1537,19 @@ pref("network.autodial-helper.enabled", 
 pref("advanced.system.supportDDEExec", true);
 
 // Use CP932 compatible map for JIS X 0208
 pref("intl.jis0208.map", "CP932");
 
 // Switch the keyboard layout per window
 pref("intl.keyboard.per_window_layout", false);
 
+// Enable/Disable TSF support
+pref("intl.enable_tsf_support", false);
+
 // See bug 448927, on topmost panel, some IMEs are not usable on Windows.
 pref("ui.panel.default_level_parent", false);
 
 # WINNT
 #endif
 
 #ifdef XP_MACOSX
 // Mac specific preference defaults
--- a/view/src/nsViewManager.cpp
+++ b/view/src/nsViewManager.cpp
@@ -1244,16 +1244,17 @@ NS_IMETHODIMP nsViewManager::DispatchEve
         //Find the view whose coordinates system we're in.
         nsView* baseView = nsView::GetViewFor(aEvent->widget);
         nsView* view = baseView;
         PRBool capturedEvent = PR_FALSE;
         
         if (!NS_IS_KEY_EVENT(aEvent) && !NS_IS_IME_EVENT(aEvent) &&
             !NS_IS_CONTEXT_MENU_KEY(aEvent) && !NS_IS_FOCUS_EVENT(aEvent) &&
             !NS_IS_QUERY_CONTENT_EVENT(aEvent) && !NS_IS_PLUGIN_EVENT(aEvent) &&
+            !NS_IS_SELECTION_EVENT(aEvent) &&
              aEvent->eventStructType != NS_ACCESSIBLE_EVENT) {
           // will dispatch using coordinates. Pretty bogus but it's consistent
           // with what presshell does.
           view = GetDisplayRootFor(baseView);
         }
 
         //Find the view to which we're initially going to send the event 
         //for hittesting.
--- a/widget/Makefile.in
+++ b/widget/Makefile.in
@@ -40,13 +40,13 @@ topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 DIRS	= public src
 
 ifdef ENABLE_TESTS
-DIRS  += tests
+TOOL_DIRS  += tests
 endif
 
 include $(topsrcdir)/config/rules.mk
 
--- a/widget/public/nsGUIEvent.h
+++ b/widget/public/nsGUIEvent.h
@@ -100,16 +100,17 @@ class nsHashKey;
 #define NS_XUL_COMMAND_EVENT              32
 #define NS_QUERY_CONTENT_EVENT            33
 #ifdef MOZ_MEDIA
 #define NS_MEDIA_EVENT                    34
 #endif // MOZ_MEDIA
 #define NS_DRAG_EVENT                     35
 #define NS_NOTIFYPAINT_EVENT              36
 #define NS_SIMPLE_GESTURE_EVENT           37
+#define NS_SELECTION_EVENT                38
 
 // These flags are sort of a mess. They're sort of shared between event
 // listener flags and event flags, but only some of them. You've been
 // warned!
 #define NS_EVENT_FLAG_NONE                0x0000
 #define NS_EVENT_FLAG_TRUSTED             0x0001
 #define NS_EVENT_FLAG_BUBBLE              0x0002
 #define NS_EVENT_FLAG_CAPTURE             0x0004
@@ -339,24 +340,28 @@ class nsHashKey;
 
 // Query the content information
 #define NS_QUERY_CONTENT_EVENT_START    3200
 // Query for the selected text information, it return the selection offset,
 // selection length and selected text.
 #define NS_QUERY_SELECTED_TEXT          (NS_QUERY_CONTENT_EVENT_START)
 // Query for the text content of specified range, it returns actual lengh (if
 // the specified range is too long) and the text of the specified range.
+// Returns the entire text if requested length > actual length.
 #define NS_QUERY_TEXT_CONTENT           (NS_QUERY_CONTENT_EVENT_START + 1)
-// Query for the character rect of nth character. If there is no character at
-// the offset, the query will be failed. The offset of the result is relative
-// position from the top level widget.
-#define NS_QUERY_CHARACTER_RECT         (NS_QUERY_CONTENT_EVENT_START + 2)
 // Query for the caret rect of nth insertion point. The offset of the result is
 // relative position from the top level widget.
 #define NS_QUERY_CARET_RECT             (NS_QUERY_CONTENT_EVENT_START + 3)
+// Query for the bounding rect of a range of characters. This works on any
+// valid character range given offset and length. Result is relative to top
+// level widget coordinates
+#define NS_QUERY_TEXT_RECT              (NS_QUERY_CONTENT_EVENT_START + 4)
+// Query for the bounding rect of the current focused frame. Result is relative
+// to top level widget coordinates
+#define NS_QUERY_EDITOR_RECT             (NS_QUERY_CONTENT_EVENT_START + 5)
 
 // Video events
 #ifdef MOZ_MEDIA
 #define NS_MEDIA_EVENT_START            3300
 #define NS_LOADSTART           (NS_MEDIA_EVENT_START)
 #define NS_PROGRESS            (NS_MEDIA_EVENT_START+1)
 #define NS_LOADEDMETADATA      (NS_MEDIA_EVENT_START+2)
 #define NS_LOADEDDATA          (NS_MEDIA_EVENT_START+3)
@@ -392,16 +397,21 @@ class nsHashKey;
 #define NS_SIMPLE_GESTURE_ROTATE_UPDATE  (NS_SIMPLE_GESTURE_EVENT_START+5)
 #define NS_SIMPLE_GESTURE_ROTATE         (NS_SIMPLE_GESTURE_EVENT_START+6)
 
 // Plug-in event. This is used when a plug-in has focus and when the native
 // event needs to be passed to the focused plug-in directly.
 #define NS_PLUGIN_EVENT_START   3600
 #define NS_PLUGIN_EVENT         (NS_PLUGIN_EVENT_START)
 
+// Events to manipulate selection (nsSelectionEvent)
+#define NS_SELECTION_EVENT_START        3700
+// Clear any previous selection and set the given range as the selection
+#define NS_SELECTION_SET                (NS_SELECTION_EVENT_START)
+
 /**
  * Return status for event processors, nsEventStatus, is defined in
  * nsEvent.h.
  */
 
 /**
  * sizemode is an adjunct to widget size
  */
@@ -853,21 +863,21 @@ struct nsTextEventReply
 
 typedef struct nsTextEventReply nsTextEventReply;
 
 class nsTextEvent : public nsInputEvent
 {
 public:
   nsTextEvent(PRBool isTrusted, PRUint32 msg, nsIWidget *w)
     : nsInputEvent(isTrusted, msg, w, NS_TEXT_EVENT),
-      theText(nsnull), rangeCount(0), rangeArray(nsnull), isChar(PR_FALSE)
+      rangeCount(0), rangeArray(nsnull), isChar(PR_FALSE)
   {
   }
 
-  const PRUnichar*  theText;
+  nsString          theText;
   nsTextEventReply  theReply;
   PRUint32          rangeCount;
   // Note that the range array may not specify a caret position; in that
   // case there will be no range of type NS_TEXTRANGE_CARETPOSITION in the
   // array.
   nsTextRangeArray  rangeArray;
   PRBool            isChar;
 };
@@ -960,45 +970,62 @@ public:
   void InitForQueryTextContent(PRUint32 aOffset, PRUint32 aLength)
   {
     NS_ASSERTION(message == NS_QUERY_TEXT_CONTENT,
                  "wrong initializer is called");
     mInput.mOffset = aOffset;
     mInput.mLength = aLength;
   }
 
-  void InitForQueryCharacterRect(PRUint32 aOffset)
-  {
-    NS_ASSERTION(message == NS_QUERY_CHARACTER_RECT,
-                 "wrong initializer is called");
-    mInput.mOffset = aOffset;
-  }
-
   void InitForQueryCaretRect(PRUint32 aOffset)
   {
     NS_ASSERTION(message == NS_QUERY_CARET_RECT,
                  "wrong initializer is called");
     mInput.mOffset = aOffset;
   }
 
+  void InitForQueryTextRect(PRUint32 aOffset, PRUint32 aLength)
+  {
+    NS_ASSERTION(message == NS_QUERY_TEXT_RECT,
+                 "wrong initializer is called");
+    mInput.mOffset = aOffset;
+    mInput.mLength = aLength;
+  }
+
   PRBool mSucceeded;
   struct {
     PRUint32 mOffset;
     PRUint32 mLength;
   } mInput;
   struct {
     void* mContentsRoot;
     PRUint32 mOffset;
     nsString mString;
     nsIntRect mRect; // Finally, the coordinates is system coordinates.
     // The return widget has the caret. This is set at all query events.
     nsIWidget* mFocusedWidget;
+    PRPackedBool mReversed; // true if selection is reversed (end < start)
   } mReply;
 };
 
+class nsSelectionEvent : public nsGUIEvent
+{
+public:
+  nsSelectionEvent(PRBool aIsTrusted, PRUint32 aMsg, nsIWidget *aWidget) :
+    nsGUIEvent(aIsTrusted, aMsg, aWidget, NS_SELECTION_EVENT),
+    mSucceeded(PR_FALSE)
+  {
+  }
+
+  PRUint32 mOffset; // start offset of selection
+  PRUint32 mLength; // length of selection
+  PRPackedBool mReversed; // selection "anchor" should be in front
+  PRPackedBool mSucceeded;
+};
+
 /**
  * MenuItem event
  * 
  * When this event occurs the widget field in nsGUIEvent holds the "target"
  * for the event
  */
 
 class nsMenuEvent : public nsGUIEvent
@@ -1227,18 +1254,22 @@ enum nsDragDropEventStatus {
         ((evnt)->message == NS_LOSTFOCUS) ||  \
         ((evnt)->message == NS_ACTIVATE) || \
         ((evnt)->message == NS_DEACTIVATE) || \
         ((evnt)->message == NS_PLUGIN_ACTIVATE))
 
 #define NS_IS_QUERY_CONTENT_EVENT(evnt) \
        (((evnt)->message == NS_QUERY_SELECTED_TEXT) || \
         ((evnt)->message == NS_QUERY_TEXT_CONTENT) || \
-        ((evnt)->message == NS_QUERY_CHARACTER_RECT) || \
-        ((evnt)->message == NS_QUERY_CARET_RECT))
+        ((evnt)->message == NS_QUERY_CARET_RECT) || \
+        ((evnt)->message == NS_QUERY_TEXT_RECT) || \
+        ((evnt)->message == NS_QUERY_EDITOR_RECT))
+
+#define NS_IS_SELECTION_EVENT(evnt) \
+       (((evnt)->message == NS_SELECTION_SET))
 
 #define NS_IS_PLUGIN_EVENT(evnt) \
        (((evnt)->message == NS_PLUGIN_EVENT))
 
 #define NS_IS_TRUSTED_EVENT(event) \
   (((event)->flags & NS_EVENT_FLAG_TRUSTED) != 0)
 
 // Mark an event as being dispatching.
--- a/widget/public/nsIWidget.h
+++ b/widget/public/nsIWidget.h
@@ -88,21 +88,24 @@ typedef nsEventStatus (* EVENT_CALLBACK)
 #define NS_NATIVE_OFFSETY     7
 #define NS_NATIVE_PLUGIN_PORT 8
 #define NS_NATIVE_SCREEN      9
 #define NS_NATIVE_SHELLWIDGET 10      // Get the shell GtkWidget
 #ifdef XP_MACOSX
 #define NS_NATIVE_PLUGIN_PORT_QD    100
 #define NS_NATIVE_PLUGIN_PORT_CG    101
 #endif
+#ifdef XP_WIN
+#define NS_NATIVE_TSF_POINTER       100
+#endif
 
-// a85944af-7fce-4e45-bf04-ac12c823394b
+// 075a7792-6ba9-454e-b431-25a43fdbd3f6
 #define NS_IWIDGET_IID \
-{ 0xa85944af, 0x7fce, 0x4e45, \
-  { 0xbf, 0x04, 0xac, 0x12, 0xc8, 0x23, 0x39, 0x4b } }
+{ 0x075a7792, 0x6ba9, 0x454e, \
+  { 0xb4, 0x31, 0x25, 0xa4, 0x3f, 0xdb, 0xd3, 0xf6 } }
 
 // Hide the native window systems real window type so as to avoid
 // including native window system types and APIs. This is necessary
 // to ensure cross-platform code.
 typedef void* nsNativeWidget;
 
 /*
  * Window shadow styles
@@ -1237,16 +1240,39 @@ class nsIWidget : public nsISupports {
      * NS_VK_SCROLL_LOCK.
      * aLEDState is the result for current LED state of the key.
      * If the LED is 'ON', it returns TRUE, otherwise, FALSE.
      * If the platform doesn't support the LED state (or we cannot get the
      * state), this method returns NS_ERROR_NOT_IMPLEMENTED.
      */
     NS_IMETHOD GetToggledKeyState(PRUint32 aKeyCode, PRBool* aLEDState) = 0;
 
+    /*
+     * An editable node (i.e. input/textarea/design mode document)
+     *  is receiving or giving up focus
+     * aFocus is true if node is receiving focus
+     * aFocus is false if node is giving up focus (blur)
+     */
+    NS_IMETHOD OnIMEFocusChange(PRBool aFocus) = 0;
+
+    /*
+     * Text content of the focused node has changed
+     * aStart is the starting offset of the change
+     * aOldEnd is the ending offset of the change
+     * aNewEnd is the caret offset after the change
+     */
+    NS_IMETHOD OnIMETextChange(PRUint32 aStart,
+                               PRUint32 aOldEnd,
+                               PRUint32 aNewEnd) = 0;
+
+    /*
+     * Selection has changed in the focused node
+     */
+    NS_IMETHOD OnIMESelectionChange(void) = 0;
+
 protected:
     // keep the list of children.  We also keep track of our siblings.
     // The ownership model is as follows: parent holds a strong ref to
     // the first element of the list, and each element holds a strong
     // ref to the next element in the list.  The prevsibling and
     // lastchild pointers are weak, which is fine as long as they are
     // maintained properly.
     nsCOMPtr<nsIWidget> mFirstChild;
--- a/widget/src/cocoa/nsChildView.mm
+++ b/widget/src/cocoa/nsChildView.mm
@@ -5485,18 +5485,18 @@ GetUSLayoutCharFromKeyTranslate(UInt32 a
 
   NSRect rect;
   if (!mGeckoChild || theRange.location == NSNotFound)
     return rect;
 
   nsIntRect r;
   PRBool useCaretRect = theRange.length == 0;
   if (!useCaretRect) {
-    nsQueryContentEvent charRect(PR_TRUE, NS_QUERY_CHARACTER_RECT, mGeckoChild);
-    charRect.InitForQueryCharacterRect(theRange.location);
+    nsQueryContentEvent charRect(PR_TRUE, NS_QUERY_TEXT_RECT, mGeckoChild);
+    charRect.InitForQueryTextRect(theRange.location, 1);
     mGeckoChild->DispatchWindowEvent(charRect);
     if (charRect.mSucceeded)
       r = charRect.mReply.mRect;
     else
       useCaretRect = PR_TRUE;
   }
 
   if (useCaretRect) {
--- a/widget/src/windows/Makefile.in
+++ b/widget/src/windows/Makefile.in
@@ -102,16 +102,17 @@ CPPSRCS += \
 	nsNativeDragTarget.cpp  \
 	nsNativeDragSource.cpp  \
 	nsDragService.cpp  \
 	nsClipboard.cpp      \
 	nsImageClipboard.cpp \
 	nsBidiKeyboard.cpp   \
 	nsSound.cpp          \
 	nsIdleServiceWin.cpp    \
+	nsTextStore.cpp \
 	$(NULL)
 endif
 
 
 DEFINES		+= -D_IMPL_NS_WIDGET -DMOZ_UNICODE 
 
 ifdef BUILD_STATIC_LIBS
 DEFINES		+= -DMOZ_STATIC_COMPONENT_LIBS
new file mode 100644
--- /dev/null
+++ b/widget/src/windows/nsTextStore.cpp
@@ -0,0 +1,1274 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ningjie Chen <chenn@email.uc.edu>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include <olectl.h>
+
+#include "nscore.h"
+#include "nsTextStore.h"
+#include "nsWindow.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "prlog.h"
+
+/******************************************************************/
+/* nsTextStore                                                    */
+/******************************************************************/
+
+ITfThreadMgr* nsTextStore::sTsfThreadMgr = NULL;
+DWORD         nsTextStore::sTsfClientId  = 0;
+nsTextStore*  nsTextStore::sTsfTextStore = NULL;
+
+UINT nsTextStore::sFlushTIPInputMessage  = 0;
+
+#ifdef PR_LOGGING
+PRLogModuleInfo* sTextStoreLog = nsnull;
+#endif
+
+nsTextStore::nsTextStore()
+{
+  mRefCnt = 1;
+  mEditCookie = 0;
+  mSinkMask = 0;
+  mWindow = nsnull;
+  mLock = 0;
+  mLockQueued = 0;
+  mTextChange.acpStart = PR_INT32_MAX;
+  mTextChange.acpOldEnd = mTextChange.acpNewEnd = 0;
+}
+
+nsTextStore::~nsTextStore()
+{
+}
+
+PRBool
+nsTextStore::Create(nsWindow* aWindow,
+                    PRUint32 aIMEState)
+{
+  if (!mDocumentMgr) {
+    // Create document manager
+    HRESULT hr = sTsfThreadMgr->CreateDocumentMgr(
+                                    getter_AddRefs(mDocumentMgr));
+    NS_ENSURE_TRUE(SUCCEEDED(hr), PR_FALSE);
+    mWindow = aWindow;
+    // Create context and add it to document manager
+    hr = mDocumentMgr->CreateContext(sTsfClientId, 0,
+                                     static_cast<ITextStoreACP*>(this),
+                                     getter_AddRefs(mContext), &mEditCookie);
+    if (SUCCEEDED(hr)) {
+      SetIMEEnabledInternal(aIMEState);
+      hr = mDocumentMgr->Push(mContext);
+    }
+    if (SUCCEEDED(hr)) {
+      PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+             ("TSF: Created, window=%08x\n", aWindow));
+      return PR_TRUE;
+    }
+    mContext = NULL;
+    mDocumentMgr = NULL;
+  }
+  return PR_FALSE;
+}
+
+PRBool
+nsTextStore::Destroy(void)
+{
+  Blur();
+  if (mWindow) {
+    // When blurred, Tablet Input Panel posts "blur" messages
+    // and try to insert text when the message is retrieved later.
+    // But by that time the text store is already destroyed,
+    // so try to get the message early
+    MSG msg;
+    if (::PeekMessageW(&msg, mWindow->GetWindowHandle(),
+                       sFlushTIPInputMessage, sFlushTIPInputMessage,
+                       PM_REMOVE)) {
+      ::DispatchMessageW(&msg);
+    }
+  }
+  mContext = NULL;
+  if (mDocumentMgr) {
+    mDocumentMgr->Pop(TF_POPF_ALL);
+    mDocumentMgr = NULL;
+  }
+  mSink = NULL;
+  PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+         ("TSF: Destroyed, window=%08x\n", mWindow));
+  mWindow = NULL;
+  return PR_TRUE;
+}
+
+PRBool
+nsTextStore::Focus(void)
+{
+  HRESULT hr = sTsfThreadMgr->SetFocus(mDocumentMgr);
+  NS_ENSURE_TRUE(SUCCEEDED(hr), PR_FALSE);
+  PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+         ("TSF: Focused\n"));
+  return PR_TRUE;
+}
+
+PRBool
+nsTextStore::Blur(void)
+{
+  sTsfThreadMgr->SetFocus(NULL);
+  PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+         ("TSF: Blurred\n"));
+  return PR_TRUE;
+}
+
+STDMETHODIMP
+nsTextStore::QueryInterface(REFIID riid,
+                            void** ppv)
+{
+  *ppv=NULL;
+  if ( (IID_IUnknown == riid) || (IID_ITextStoreACP == riid) ) {
+    *ppv = static_cast<ITextStoreACP*>(this);
+  } else if (IID_ITfContextOwnerCompositionSink == riid) {
+    *ppv = static_cast<ITfContextOwnerCompositionSink*>(this);
+  }
+  if (*ppv) {
+    AddRef();
+    return S_OK;
+  }
+  return E_NOINTERFACE;
+}
+
+STDMETHODIMP_(ULONG) nsTextStore::AddRef()
+{
+  return ++mRefCnt;
+}
+
+STDMETHODIMP_(ULONG) nsTextStore::Release()
+{
+  --mRefCnt;
+  if (0 != mRefCnt)
+    return mRefCnt;
+  delete this;
+  return 0;
+}
+
+STDMETHODIMP
+nsTextStore::AdviseSink(REFIID riid,
+                        IUnknown *punk,
+                        DWORD dwMask)
+{
+  NS_ENSURE_TRUE(punk && IID_ITextStoreACPSink == riid, E_INVALIDARG);
+  if (!mSink) {
+    // Install sink
+    punk->QueryInterface(IID_ITextStoreACPSink, getter_AddRefs(mSink));
+    NS_ENSURE_TRUE(mSink, E_UNEXPECTED);
+  } else {
+    // If sink is already installed we check to see if they are the same
+    // Get IUnknown from both sides for comparison
+    nsRefPtr<IUnknown> comparison1, comparison2;
+    punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
+    mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
+    if (comparison1 != comparison2)
+      return CONNECT_E_ADVISELIMIT;
+  }
+  // Update mask either for a new sink or an existing sink
+  mSinkMask = dwMask;
+  PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+         ("TSF: Sink installed, punk=%08x\n", punk));
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::UnadviseSink(IUnknown *punk)
+{
+  NS_ENSURE_TRUE(punk, E_INVALIDARG);
+  NS_ENSURE_TRUE(mSink, CONNECT_E_NOCONNECTION);
+  // Get IUnknown from both sides for comparison
+  nsRefPtr<IUnknown> comparison1, comparison2;
+  punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
+  mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
+  // Unadvise only if sinks are the same
+  NS_ENSURE_TRUE(comparison1 == comparison2, CONNECT_E_NOCONNECTION);
+  mSink = NULL;
+  mSinkMask = 0;
+  PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+         ("TSF: Sink removed, punk=%08x\n", punk));
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::RequestLock(DWORD dwLockFlags,
+                         HRESULT *phrSession)
+{
+  NS_ENSURE_TRUE(mSink, E_FAIL);
+  NS_ENSURE_TRUE(phrSession, E_INVALIDARG);
+  if (mLock) {
+    // only time when reentrant lock is allowed is when caller holds a
+    // read-only lock and is requesting an async write lock
+    if (TS_LF_READ == (mLock & TS_LF_READWRITE) &&
+        TS_LF_READWRITE == (dwLockFlags & TS_LF_READWRITE) &&
+        !(dwLockFlags & TS_LF_SYNC)) {
+      *phrSession = TS_S_ASYNC;
+      mLockQueued = dwLockFlags & (~TS_LF_SYNC);
+    } else {
+      // no more locks allowed
+      *phrSession = TS_E_SYNCHRONOUS;
+      return E_FAIL;
+    }
+  } else {
+    // put on lock
+    mLock = dwLockFlags & (~TS_LF_SYNC);
+    *phrSession = mSink->OnLockGranted(mLock);
+    while (mLockQueued) {
+      mLock = mLockQueued;
+      mLockQueued = 0;
+      mSink->OnLockGranted(mLock);
+    }
+    mLock = 0;
+  }
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::GetStatus(TS_STATUS *pdcs)
+{
+  NS_ENSURE_TRUE(pdcs, E_INVALIDARG);
+  pdcs->dwDynamicFlags = 0;
+  // we use a "flat" text model for TSF support so no hidden text
+  pdcs->dwStaticFlags = TS_SS_NOHIDDENTEXT;
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::QueryInsert(LONG acpTestStart,
+                         LONG acpTestEnd,
+                         ULONG cch,
+                         LONG *pacpResultStart,
+                         LONG *pacpResultEnd)
+{
+  PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+         ("TSF: QueryInsert, start=%ld end=%ld cch=%lu\n",
+          acpTestStart, acpTestEnd, cch));
+  // We don't test to see if these positions are
+  // after the end of document for performance reasons
+  NS_ENSURE_TRUE(0 <= acpTestStart && acpTestStart <= acpTestEnd &&
+                 pacpResultStart && pacpResultEnd, E_INVALIDARG);
+
+  // XXX need to adjust to cluster boundary
+  // Assume we are given good offsets for now
+  *pacpResultStart = acpTestStart;
+  *pacpResultEnd = acpTestStart + cch;
+
+  PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+         ("TSF: QueryInsert SUCCEEDED\n"));
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::GetSelection(ULONG ulIndex,
+                          ULONG ulCount,
+                          TS_SELECTION_ACP *pSelection,
+                          ULONG *pcFetched)
+{
+  NS_ENSURE_TRUE(TS_LF_READ == (mLock & TS_LF_READ), TS_E_NOLOCK);
+  NS_ENSURE_TRUE(ulCount && pSelection && pcFetched, E_INVALIDARG);
+
+  *pcFetched = 0;
+  NS_ENSURE_TRUE(TS_DEFAULT_SELECTION == ulIndex || 0 == ulIndex,
+                 TS_E_NOSELECTION);
+  if (mCompositionView) {
+    // Emulate selection during compositions
+    *pSelection = mCompositionSelection;
+  } else {
+    // Construct and initialize an event to get selection info
+    nsQueryContentEvent event(PR_TRUE, NS_QUERY_SELECTED_TEXT, mWindow);
+    mWindow->InitEvent(event);
+    mWindow->DispatchWindowEvent(&event);
+    NS_ENSURE_TRUE(event.mSucceeded, E_FAIL);
+    // Usually the selection anchor (beginning) position corresponds to the
+    // TSF start and the selection focus (ending) position corresponds to
+    // the TSF end, but if selection is reversed the focus now corresponds
+    // to the TSF start and the anchor now corresponds to the TSF end
+    pSelection->acpStart = event.mReply.mOffset;
+    pSelection->acpEnd = pSelection->acpStart + event.mReply.mString.Length();
+    pSelection->style.ase = event.mReply.mString.Length() &&
+        event.mReply.mReversed ? TS_AE_START : TS_AE_END;
+    // No support for interim character
+    pSelection->style.fInterimChar = 0;
+  }
+  *pcFetched = 1;
+  return S_OK;
+}
+
+HRESULT
+nsTextStore::SetSelectionInternal(const TS_SELECTION_ACP* pSelection)
+{
+  PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+         ("TSF: SetSelection, sel=%ld-%ld\n",
+          pSelection->acpStart, pSelection->acpEnd));
+  if (mCompositionView) {
+    // Emulate selection during compositions
+    NS_ENSURE_TRUE(pSelection->acpStart >= mCompositionStart &&
+                   pSelection->acpEnd <= mCompositionStart +
+                       LONG(mCompositionString.Length()), TS_E_INVALIDPOS);
+    mCompositionSelection = *pSelection;
+  } else {
+    nsSelectionEvent event(PR_TRUE, NS_SELECTION_SET, mWindow);
+    event.mOffset = pSelection->acpStart;
+    event.mLength = PRUint32(pSelection->acpEnd - pSelection->acpStart);
+    event.mReversed = pSelection->style.ase == TS_AE_START;
+    mWindow->InitEvent(event);
+    mWindow->DispatchWindowEvent(&event);
+    NS_ENSURE_TRUE(event.mSucceeded, E_FAIL);
+  }
+  PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+         ("TSF: SetSelection SUCCEEDED\n"));
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::SetSelection(ULONG ulCount,
+                          const TS_SELECTION_ACP *pSelection)
+{
+  NS_ENSURE_TRUE(TS_LF_READWRITE == (mLock & TS_LF_READWRITE), TS_E_NOLOCK);
+  NS_ENSURE_TRUE(1 == ulCount && pSelection, E_INVALIDARG);
+
+  return SetSelectionInternal(pSelection);
+}
+
+STDMETHODIMP
+nsTextStore::GetText(LONG acpStart,
+                     LONG acpEnd,
+                     WCHAR *pchPlain,
+                     ULONG cchPlainReq,
+                     ULONG *pcchPlainOut,
+                     TS_RUNINFO *prgRunInfo,
+                     ULONG ulRunInfoReq,
+                     ULONG *pulRunInfoOut,
+                     LONG *pacpNext)
+{
+  NS_ENSURE_TRUE(TS_LF_READ == (mLock & TS_LF_READ), TS_E_NOLOCK);
+  NS_ENSURE_TRUE(pcchPlainOut && (pchPlain || prgRunInfo) &&
+                 (!cchPlainReq == !pchPlain) &&
+                 (!ulRunInfoReq == !prgRunInfo), E_INVALIDARG);
+  NS_ENSURE_TRUE(0 <= acpStart && -1 <= acpEnd &&
+                 (-1 == acpEnd || acpStart <= acpEnd), TS_E_INVALIDPOS);
+
+  // Making sure to NULL-terminate string just to be on the safe side
+  *pcchPlainOut = 0;
+  if (pchPlain && cchPlainReq) *pchPlain = NULL;
+  if (pulRunInfoOut) *pulRunInfoOut = 0;
+  if (pacpNext) *pacpNext = acpStart;
+  if (prgRunInfo && ulRunInfoReq) {
+    prgRunInfo->uCount = 0;
+    prgRunInfo->type = TS_RT_PLAIN;
+  }
+  PRUint32 length = -1 == acpEnd ? PR_UINT32_MAX : PRUint32(acpEnd - acpStart);
+  if (cchPlainReq && cchPlainReq - 1 < length) {
+    length = cchPlainReq - 1;
+  }
+  if (length) {
+    LONG compNewStart = 0, compOldEnd = 0, compNewEnd = 0;
+    if (mCompositionView) {
+      // Sometimes GetText gets called between InsertTextAtSelection and
+      // OnUpdateComposition. In this case the returned text would
+      // be out of sync because we haven't sent NS_TEXT_TEXT in
+      // OnUpdateComposition yet. Manually resync here.
+      compOldEnd = PR_MIN(LONG(length) + acpStart,
+                       mCompositionLength + mCompositionStart);
+      compNewEnd = PR_MIN(LONG(length) + acpStart,
+                       LONG(mCompositionString.Length()) + mCompositionStart);
+      compNewStart = PR_MAX(acpStart, mCompositionStart);
+      // Check if the range is affected
+      if (compOldEnd > compNewStart || compNewEnd > compNewStart) {
+        NS_ASSERTION(compOldEnd >= mCompositionStart &&
+            compNewEnd >= mCompositionStart, "Range end is less than start\n");
+        length = PRUint32(LONG(length) + compOldEnd - compNewEnd);
+      }
+    }
+    // Send NS_QUERY_TEXT_CONTENT to get text content
+    nsQueryContentEvent event(PR_TRUE, NS_QUERY_TEXT_CONTENT, mWindow);
+    mWindow->InitEvent(event);
+    event.InitForQueryTextContent(PRUint32(acpStart), length);
+    mWindow->DispatchWindowEvent(&event);
+    NS_ENSURE_TRUE(event.mSucceeded, E_FAIL);
+
+    if (compOldEnd > compNewStart || compNewEnd > compNewStart) {
+      // Resync composition string
+      const PRUnichar* compStrStart = mCompositionString.BeginReading() +
+          PR_MAX(compNewStart - mCompositionStart, 0);
+      event.mReply.mString.Replace(compNewStart - acpStart,
+          compOldEnd - mCompositionStart, compStrStart,
+          compNewEnd - mCompositionStart);
+      length = PRUint32(LONG(length) - compOldEnd + compNewEnd);
+    }
+    NS_ENSURE_TRUE(-1 == acpEnd || event.mReply.mString.Length() == length,
+                   TS_E_INVALIDPOS);
+    length = PR_MIN(length, event.mReply.mString.Length());
+
+    if (pchPlain && cchPlainReq) {
+      memcpy(pchPlain, event.mReply.mString.BeginReading(),
+             length * sizeof(*pchPlain));
+      pchPlain[length] = NULL;
+      *pcchPlainOut = length;
+    }
+    if (prgRunInfo && ulRunInfoReq) {
+      prgRunInfo->uCount = length;
+      prgRunInfo->type = TS_RT_PLAIN;
+      if (pulRunInfoOut) *pulRunInfoOut = 1;
+    }
+    if (pacpNext) *pacpNext = acpStart + length;
+  }
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::SetText(DWORD dwFlags,
+                     LONG acpStart,
+                     LONG acpEnd,
+                     const WCHAR *pchText,
+                     ULONG cch,
+                     TS_TEXTCHANGE *pChange)
+{
+  // Per SDK documentation, and since we don't have better
+  // ways to do this, this method acts as a helper to
+  // call SetSelection followed by InsertTextAtSelection
+  NS_ENSURE_TRUE(TS_LF_READWRITE == (mLock & TS_LF_READWRITE), TS_E_NOLOCK);
+  TS_SELECTION_ACP selection;
+  selection.acpStart = acpStart;
+  selection.acpEnd = acpEnd;
+  selection.style.ase = TS_AE_END;
+  selection.style.fInterimChar = 0;
+  // Set selection to desired range
+  NS_ENSURE_TRUE(SUCCEEDED(SetSelectionInternal(&selection)), E_FAIL);
+  // Replace just selected text
+  return InsertTextAtSelection(TS_IAS_NOQUERY, pchText, cch,
+                               NULL, NULL, pChange);
+}
+
+STDMETHODIMP
+nsTextStore::GetFormattedText(LONG acpStart,
+                              LONG acpEnd,
+                              IDataObject **ppDataObject)
+{
+  // no support for formatted text
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP
+nsTextStore::GetEmbedded(LONG acpPos,
+                         REFGUID rguidService,
+                         REFIID riid,
+                         IUnknown **ppunk)
+{
+  // embedded objects are not supported
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP
+nsTextStore::QueryInsertEmbedded(const GUID *pguidService,
+                                 const FORMATETC *pFormatEtc,
+                                 BOOL *pfInsertable)
+{
+  // embedded objects are not supported
+  *pfInsertable = FALSE;
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::InsertEmbedded(DWORD dwFlags,
+                            LONG acpStart,
+                            LONG acpEnd,
+                            IDataObject *pDataObject,
+                            TS_TEXTCHANGE *pChange)
+{
+  // embedded objects are not supported
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP
+nsTextStore::RequestSupportedAttrs(DWORD dwFlags,
+                                   ULONG cFilterAttrs,
+                                   const TS_ATTRID *paFilterAttrs)
+{
+  // no attributes defined
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::RequestAttrsAtPosition(LONG acpPos,
+                                    ULONG cFilterAttrs,
+                                    const TS_ATTRID *paFilterAttrs,
+                                    DWORD dwFlags)
+{
+  // no per character attributes defined
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::RequestAttrsTransitioningAtPosition(LONG acpPos,
+                                                 ULONG cFilterAttrs,
+                                                 const TS_ATTRID *paFilterAttr,
+                                                 DWORD dwFlags)
+{
+  // no per character attributes defined
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::FindNextAttrTransition(LONG acpStart,
+                                    LONG acpHalt,
+                                    ULONG cFilterAttrs,
+                                    const TS_ATTRID *paFilterAttrs,
+                                    DWORD dwFlags,
+                                    LONG *pacpNext,
+                                    BOOL *pfFound,
+                                    LONG *plFoundOffset)
+{
+  NS_ENSURE_TRUE(pacpNext && pfFound && plFoundOffset, E_INVALIDARG);
+  // no per character attributes defined
+  *pacpNext = *plFoundOffset = acpHalt;
+  *pfFound = FALSE;
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::RetrieveRequestedAttrs(ULONG ulCount,
+                                    TS_ATTRVAL *paAttrVals,
+                                    ULONG *pcFetched)
+{
+  NS_ENSURE_TRUE(pcFetched && ulCount && paAttrVals, E_INVALIDARG);
+  // no attributes defined
+  *pcFetched = 0;
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::GetEndACP(LONG *pacp)
+{
+  NS_ENSURE_TRUE(TS_LF_READ == (mLock & TS_LF_READ), TS_E_NOLOCK);
+  NS_ENSURE_TRUE(pacp, E_INVALIDARG);
+  // Flattened text is retrieved and its length returned
+  nsQueryContentEvent event(PR_TRUE, NS_QUERY_TEXT_CONTENT, mWindow);
+  mWindow->InitEvent(event);
+  // Return entire text
+  event.InitForQueryTextContent(0, PR_INT32_MAX);
+  mWindow->DispatchWindowEvent(&event);
+  NS_ENSURE_TRUE(event.mSucceeded, E_FAIL);
+  *pacp = LONG(event.mReply.mString.Length());
+  return S_OK;
+}
+
+#define TEXTSTORE_DEFAULT_VIEW    (1)
+
+STDMETHODIMP
+nsTextStore::GetActiveView(TsViewCookie *pvcView)
+{
+  NS_ENSURE_TRUE(pvcView, E_INVALIDARG);
+  *pvcView = TEXTSTORE_DEFAULT_VIEW;
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::GetACPFromPoint(TsViewCookie vcView,
+                             const POINT *pt,
+                             DWORD dwFlags,
+                             LONG *pacp)
+{
+  NS_ENSURE_TRUE(TS_LF_READ == (mLock & TS_LF_READ), TS_E_NOLOCK);
+  NS_ENSURE_TRUE(TEXTSTORE_DEFAULT_VIEW == vcView, E_INVALIDARG);
+  // not supported for now
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP
+nsTextStore::GetTextExt(TsViewCookie vcView,
+                        LONG acpStart,
+                        LONG acpEnd,
+                        RECT *prc,
+                        BOOL *pfClipped)
+{
+  NS_ENSURE_TRUE(TS_LF_READ == (mLock & TS_LF_READ), TS_E_NOLOCK);
+  NS_ENSURE_TRUE(TEXTSTORE_DEFAULT_VIEW == vcView && prc && pfClipped,
+                 E_INVALIDARG);
+  NS_ENSURE_TRUE(acpStart >= 0 && acpEnd >= acpStart, TS_E_INVALIDPOS);
+
+  // use NS_QUERY_TEXT_RECT to get rect in system, screen coordinates
+  nsQueryContentEvent event(PR_TRUE, NS_QUERY_TEXT_RECT, mWindow);
+  mWindow->InitEvent(event);
+  event.InitForQueryTextRect(acpStart, acpEnd - acpStart);
+  mWindow->DispatchWindowEvent(&event);
+  NS_ENSURE_TRUE(event.mSucceeded, TS_E_INVALIDPOS);
+  // IMEs don't like empty rects, fix here
+  if (event.mReply.mRect.width <= 0)
+    event.mReply.mRect.width = 1;
+  if (event.mReply.mRect.height <= 0)
+    event.mReply.mRect.height = 1;
+
+  // convert to unclipped screen rect
+  nsWindow* refWindow = static_cast<nsWindow*>(
+      event.mReply.mFocusedWidget ? event.mReply.mFocusedWidget : mWindow);
+  // Result rect is in top level widget coordinates
+  refWindow = refWindow->GetTopLevelWindow(PR_FALSE);
+  NS_ENSURE_TRUE(refWindow, E_FAIL);
+
+  nsresult rv = refWindow->WidgetToScreen(event.mReply.mRect,
+                                          event.mReply.mRect);
+  NS_ENSURE_SUCCESS(rv, E_FAIL);
+
+  // get bounding screen rect to test for clipping
+  HRESULT hr = GetScreenExt(vcView, prc);
+  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+  // clip text rect to bounding rect
+  RECT textRect;
+  ::SetRect(&textRect, event.mReply.mRect.x, event.mReply.mRect.y,
+            event.mReply.mRect.XMost(), event.mReply.mRect.YMost());
+  if (!::IntersectRect(prc, prc, &textRect))
+    // Text is not visible
+    ::SetRectEmpty(prc);
+
+  // not equal if text rect was clipped
+  *pfClipped = !::EqualRect(prc, &textRect);
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::GetScreenExt(TsViewCookie vcView,
+                          RECT *prc)
+{
+  NS_ENSURE_TRUE(TEXTSTORE_DEFAULT_VIEW == vcView && prc, E_INVALIDARG);
+  // use NS_QUERY_EDITOR_RECT to get rect in system, screen coordinates
+  nsQueryContentEvent event(PR_TRUE, NS_QUERY_EDITOR_RECT, mWindow);
+  mWindow->InitEvent(event);
+  mWindow->DispatchWindowEvent(&event);
+  NS_ENSURE_TRUE(event.mSucceeded, E_FAIL);
+
+  nsWindow* refWindow = static_cast<nsWindow*>(
+      event.mReply.mFocusedWidget ? event.mReply.mFocusedWidget : mWindow);
+  // Result rect is in top level widget coordinates
+  refWindow = refWindow->GetTopLevelWindow(PR_FALSE);
+  NS_ENSURE_TRUE(refWindow, E_FAIL);
+
+  nsIntRect boundRect;
+  nsresult rv = refWindow->GetClientBounds(boundRect);
+  NS_ENSURE_SUCCESS(rv, E_FAIL);
+
+  // Clip frame rect to window rect
+  boundRect.IntersectRect(event.mReply.mRect, boundRect);
+  rv = refWindow->WidgetToScreen(boundRect, boundRect);
+  NS_ENSURE_SUCCESS(rv, E_FAIL);
+
+  if (!boundRect.IsEmpty()) {
+    ::SetRect(prc, boundRect.x, boundRect.y,
+              boundRect.XMost(), boundRect.YMost());
+  } else {
+    ::SetRectEmpty(prc);
+  }
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::GetWnd(TsViewCookie vcView,
+                    HWND *phwnd)
+{
+  NS_ENSURE_TRUE(TEXTSTORE_DEFAULT_VIEW == vcView && phwnd, E_INVALIDARG);
+  *phwnd = mWindow->GetWindowHandle();
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::InsertTextAtSelection(DWORD dwFlags,
+                                   const WCHAR *pchText,
+                                   ULONG cch,
+                                   LONG *pacpStart,
+                                   LONG *pacpEnd,
+                                   TS_TEXTCHANGE *pChange)
+{
+  PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+         ("TSF: InsertTextAtSelection, cch=%lu\n", cch));
+  NS_ENSURE_TRUE(TS_LF_READWRITE == (mLock & TS_LF_READWRITE), TS_E_NOLOCK);
+  NS_ENSURE_TRUE(!cch || pchText, E_INVALIDARG);
+
+  // Get selection first
+  TS_SELECTION_ACP sel;
+  ULONG selFetched;
+  NS_ENSURE_TRUE(SUCCEEDED(GetSelection(
+      TS_DEFAULT_SELECTION, 1, &sel, &selFetched)) && selFetched, E_FAIL);
+  if (TS_IAS_QUERYONLY == dwFlags) {
+    NS_ENSURE_TRUE(pacpStart && pacpEnd, E_INVALIDARG);
+    // Simulate text insertion
+    *pacpStart = sel.acpStart;
+    *pacpEnd = sel.acpEnd;
+    if (pChange) {
+      pChange->acpStart = sel.acpStart;
+      pChange->acpOldEnd = sel.acpEnd;
+      pChange->acpNewEnd = sel.acpStart + cch;
+    }
+  } else {
+    NS_ENSURE_TRUE(pChange, E_INVALIDARG);
+    NS_ENSURE_TRUE(TS_IAS_NOQUERY == dwFlags || (pacpStart && pacpEnd),
+                   E_INVALIDARG);
+    if (mCompositionView) {
+      // Emulate text insertion during compositions, because during a
+      // composition, editor expects the whole composition string to
+      // be sent in NS_TEXT_TEXT, not just the inserted part.
+      // The actual NS_TEXT_TEXT is sent in OnUpdateComposition, which
+      // should get called by TSF after this returns
+      mCompositionString.Replace(PRUint32(sel.acpStart - mCompositionStart),
+                                 sel.acpEnd - sel.acpStart, pchText, cch);
+
+      mCompositionSelection.acpStart += cch;
+      mCompositionSelection.acpEnd = mCompositionSelection.acpStart;
+      mCompositionSelection.style.ase = TS_AE_END;
+      // OnUpdateComposition is not called here because it will
+      // result in fun visual artifacts
+      PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+             ("TSF: InsertTextAtSelection, replaced=%lu-%lu\n",
+              sel.acpStart - mCompositionStart,
+              sel.acpEnd - mCompositionStart));
+    } else {
+      // Use a temporary composition to contain the text
+      nsCompositionEvent compEvent(PR_TRUE, NS_COMPOSITION_START, mWindow);
+      mWindow->InitEvent(compEvent);
+      mWindow->DispatchWindowEvent(&compEvent);
+      nsTextEvent event(PR_TRUE, NS_TEXT_TEXT, mWindow);
+      mWindow->InitEvent(event);
+      if (!cch) {
+        // XXX See OnEndComposition comment on inserting empty strings
+        event.theText = NS_LITERAL_STRING(" ");
+        mWindow->DispatchWindowEvent(&event);
+      }
+      event.theText.Assign(pchText, cch);
+      event.theText.ReplaceSubstring(NS_LITERAL_STRING("\r\n"),
+                                     NS_LITERAL_STRING("\n"));
+      mWindow->DispatchWindowEvent(&event);
+      compEvent.message = NS_COMPOSITION_END;
+      mWindow->DispatchWindowEvent(&compEvent);
+    }
+    pChange->acpStart = sel.acpStart;
+    pChange->acpOldEnd = sel.acpEnd;
+    // Get new selection
+    NS_ENSURE_TRUE(SUCCEEDED(GetSelection(
+        TS_DEFAULT_SELECTION, 1, &sel, &selFetched)) && selFetched, E_FAIL);
+    pChange->acpNewEnd = sel.acpEnd;
+    if (TS_IAS_NOQUERY != dwFlags) {
+      *pacpStart = pChange->acpStart;
+      *pacpEnd = pChange->acpNewEnd;
+    }
+  }
+  PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+         ("TSF: InsertTextAtSelection SUCCEEDED\n"));
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::InsertEmbeddedAtSelection(DWORD dwFlags,
+                                       IDataObject *pDataObject,
+                                       LONG *pacpStart,
+                                       LONG *pacpEnd,
+                                       TS_TEXTCHANGE *pChange)
+{
+  // embedded objects are not supported
+  return E_NOTIMPL;
+}
+
+static HRESULT
+GetRangeExtent(ITfRange* aRange, LONG* aStart, LONG* aLength)
+{
+  nsRefPtr<ITfRangeACP> rangeACP;
+  aRange->QueryInterface(IID_ITfRangeACP, getter_AddRefs(rangeACP));
+  NS_ENSURE_TRUE(rangeACP, E_FAIL);
+  return rangeACP->GetExtent(aStart, aLength);
+}
+
+HRESULT
+nsTextStore::OnStartCompositionInternal(ITfCompositionView* pComposition,
+                                        ITfRange* aRange,
+                                        PRBool aPreserveSelection)
+{
+  mCompositionView = pComposition;
+  HRESULT hr = GetRangeExtent(aRange, &mCompositionStart, &mCompositionLength);
+  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+  PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+         ("TSF: OnStartComposition, range=%ld-%ld\n", mCompositionStart,
+          mCompositionStart + mCompositionLength));
+
+  // Select composition range so the new composition replaces the range
+  nsSelectionEvent selEvent(PR_TRUE, NS_SELECTION_SET, mWindow);
+  mWindow->InitEvent(selEvent);
+  selEvent.mOffset = PRUint32(mCompositionStart);
+  selEvent.mLength = PRUint32(mCompositionLength);
+  selEvent.mReversed = PR_FALSE;
+  mWindow->DispatchWindowEvent(&selEvent);
+  NS_ENSURE_TRUE(selEvent.mSucceeded, E_FAIL);
+
+  // Set up composition
+  nsQueryContentEvent queryEvent(PR_TRUE, NS_QUERY_SELECTED_TEXT, mWindow);
+  mWindow->InitEvent(queryEvent);
+  mWindow->DispatchWindowEvent(&queryEvent);
+  NS_ENSURE_TRUE(queryEvent.mSucceeded, E_FAIL);
+  mCompositionString = queryEvent.mReply.mString;
+  if (!aPreserveSelection) {
+    mCompositionSelection.acpStart = mCompositionStart;
+    mCompositionSelection.acpEnd = mCompositionStart + mCompositionLength;
+    mCompositionSelection.style.ase = TS_AE_END;
+    mCompositionSelection.style.fInterimChar = FALSE;
+  }
+  nsCompositionEvent event(PR_TRUE, NS_COMPOSITION_START, mWindow);
+  mWindow->InitEvent(event);
+  mWindow->DispatchWindowEvent(&event);
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::OnStartComposition(ITfCompositionView* pComposition,
+                                BOOL* pfOk)
+{
+  *pfOk = FALSE;
+
+  // Only one composition at a time
+  if (mCompositionView)
+    return S_OK;
+
+  nsRefPtr<ITfRange> range;
+  HRESULT hr = pComposition->GetRange(getter_AddRefs(range));
+  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+  hr = OnStartCompositionInternal(pComposition, range, PR_FALSE);
+  if (SUCCEEDED(hr))
+    *pfOk = TRUE;
+  return hr;
+}
+
+STDMETHODIMP
+nsTextStore::OnUpdateComposition(ITfCompositionView* pComposition,
+                                 ITfRange* pRangeNew)
+{
+  NS_ENSURE_TRUE(mCompositionView &&
+                 mCompositionView == pComposition &&
+                 mDocumentMgr && mContext, E_UNEXPECTED);
+
+  // Getting display attributes is *really* complicated!
+  // We first get the context and the property objects to query for
+  // attributes, but since a big range can have a variety of values for
+  // the attribute, we have to find out all the ranges that have distinct
+  // attribute values. Then we query for what the value represents through
+  // the display attribute manager and translate that to nsTextRange to be
+  // sent in NS_TEXT_TEXT
+  if (!pRangeNew) // pRangeNew is null when the update is not complete
+    return S_OK;
+
+  // Get starting offset of the composition
+  LONG compStart = 0, compLength = 0;
+  HRESULT hr = GetRangeExtent(pRangeNew, &compStart, &compLength);
+  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+  if (mCompositionStart != compStart ||
+      mCompositionString.Length() != compLength) {
+    // If the queried composition length is different from the length
+    // of our composition string, OnUpdateComposition is being called
+    // because a part of the original composition was committed.
+    // Reflect that by committing existing composition and starting
+    // a new one. OnEndComposition followed by OnStartComposition
+    // will accomplish this automagically.
+    OnEndComposition(pComposition);
+    OnStartCompositionInternal(pComposition, pRangeNew, PR_TRUE);
+    PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+           ("TSF: OnUpdateComposition, (reset) range=%ld-%ld\n",
+            compStart, compStart + compLength));
+  } else {
+    mCompositionLength = compLength;
+    PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+           ("TSF: OnUpdateComposition, range=%ld-%ld\n",
+            compStart, compStart + compLength));
+  }
+
+  nsRefPtr<ITfProperty> prop;
+  hr = mContext->GetProperty(GUID_PROP_ATTRIBUTE, getter_AddRefs(prop));
+  NS_ENSURE_TRUE(SUCCEEDED(hr) && prop, hr);
+  hr = LoadManagers();
+  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+  // Use NS_TEXT_TEXT to set composition string
+  nsTextEvent event(PR_TRUE, NS_TEXT_TEXT, mWindow);
+  mWindow->InitEvent(event);
+
+  VARIANT propValue;
+  ::VariantInit(&propValue);
+  nsRefPtr<ITfRange> range;
+  nsRefPtr<IEnumTfRanges> enumRanges;
+  hr = prop->EnumRanges(TfEditCookie(mEditCookie),
+                        getter_AddRefs(enumRanges), pRangeNew);
+  NS_ENSURE_TRUE(SUCCEEDED(hr) && enumRanges, hr);
+
+  nsAutoTArray<nsTextRange, 4> textRanges;
+  nsTextRange newRange;
+  newRange.mStartOffset = PRUint32(mCompositionSelection.acpStart - compStart);
+  newRange.mEndOffset = PRUint32(mCompositionSelection.acpEnd - compStart);
+  newRange.mRangeType = NS_TEXTRANGE_CARETPOSITION;
+  textRanges.AppendElement(newRange);
+  // No matter if we have display attribute info or not,
+  // we always pass in at least one range to NS_TEXT_TEXT
+  newRange.mStartOffset = 0;
+  newRange.mEndOffset = mCompositionString.Length();
+  newRange.mRangeType = NS_TEXTRANGE_RAWINPUT;
+  textRanges.AppendElement(newRange);
+
+  while (S_OK == enumRanges->Next(1, getter_AddRefs(range), NULL) && range) {
+
+    LONG start = 0, length = 0;
+    if (FAILED(GetRangeExtent(range, &start, &length))) continue;
+
+    newRange.mStartOffset = PRUint32(start - compStart);
+    // The end of the last range in the array is
+    // always kept at the end of composition
+    newRange.mEndOffset = mCompositionString.Length();
+
+    // Who came up with this convoluted way that we have to follow?
+    ::VariantClear(&propValue);
+    hr = prop->GetValue(TfEditCookie(mEditCookie), range, &propValue);
+    if (FAILED(hr) || VT_I4 != propValue.vt) continue;
+
+    GUID guid;
+    hr = mCatMgr->GetGUID(DWORD(propValue.lVal), &guid);
+    if (FAILED(hr)) continue;
+
+    nsRefPtr<ITfDisplayAttributeInfo> info;
+    hr = mDAMgr->GetDisplayAttributeInfo(
+                     guid, getter_AddRefs(info), NULL);
+    if (FAILED(hr) || !info) continue;
+
+    TF_DISPLAYATTRIBUTE attr;
+    hr = info->GetAttributeInfo(&attr);
+    if (FAILED(hr)) continue;
+
+    switch (attr.bAttr) {
+    case TF_ATTR_TARGET_CONVERTED:
+      newRange.mRangeType = NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
+      break;
+    case TF_ATTR_CONVERTED:
+      newRange.mRangeType = NS_TEXTRANGE_CONVERTEDTEXT;
+      break;
+    case TF_ATTR_TARGET_NOTCONVERTED:
+      newRange.mRangeType = NS_TEXTRANGE_SELECTEDRAWTEXT;
+      break;
+    default:
+      newRange.mRangeType = NS_TEXTRANGE_RAWINPUT;
+      break;
+    }
+
+    nsTextRange& lastRange = textRanges[textRanges.Length() - 1];
+    if (lastRange.mStartOffset == newRange.mStartOffset) {
+      // Replace range if last range is the same as this one
+      // So that ranges don't overlap and confuse the editor
+      lastRange = newRange;
+    } else {
+      lastRange.mEndOffset = newRange.mStartOffset;
+      textRanges.AppendElement(newRange);
+    }
+  }
+
+  event.theText = mCompositionString;
+  event.rangeArray = textRanges.Elements();
+  event.rangeCount = textRanges.Length();
+  mWindow->DispatchWindowEvent(&event);
+  ::VariantClear(&propValue);
+  return S_OK;
+}
+
+STDMETHODIMP
+nsTextStore::OnEndComposition(ITfCompositionView* pComposition)
+{
+  NS_ENSURE_TRUE(mCompositionView &&
+                 mCompositionView == pComposition, E_UNEXPECTED);
+  PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+         ("TSF: OnEndComposition\n"));
+
+  // Use NS_TEXT_TEXT to commit composition string
+  nsTextEvent textEvent(PR_TRUE, NS_TEXT_TEXT, mWindow);
+  mWindow->InitEvent(textEvent);
+  if (!mCompositionString.Length()) {
+    // XXX HACK! HACK! NS_TEXT_TEXT handler specifically rejects
+    // first-time empty strings as workaround for another IME bug
+    // and our request will be rejected if this is the first time
+    // we are sending NS_TEXT_TEXT. The workaround is to send it a
+    // non-empty dummy string first.
+    textEvent.theText = NS_LITERAL_STRING(" ");
+    mWindow->DispatchWindowEvent(&textEvent);
+  }
+  textEvent.theText = mCompositionString;
+  textEvent.theText.ReplaceSubstring(NS_LITERAL_STRING("\r\n"),
+                                     NS_LITERAL_STRING("\n"));
+  mWindow->DispatchWindowEvent(&textEvent);
+
+  nsCompositionEvent event(PR_TRUE, NS_COMPOSITION_END, mWindow);
+  mWindow->InitEvent(event);
+  mWindow->DispatchWindowEvent(&event);
+
+  mCompositionView = NULL;
+  mCompositionString.Truncate(0);
+  // Maintain selection
+  SetSelectionInternal(&mCompositionSelection);
+  return S_OK;
+}
+
+nsresult
+nsTextStore::OnFocusChange(PRBool aFocus,
+                           nsWindow* aWindow,
+                           PRUint32 aIMEEnabled)
+{
+  // no change notifications if TSF is disabled
+  if (!sTsfThreadMgr || !sTsfTextStore)
+    return NS_ERROR_NOT_AVAILABLE;
+
+  if (aFocus) {
+    if (sTsfTextStore->Create(aWindow, aIMEEnabled))
+      sTsfTextStore->Focus();
+  } else {
+    sTsfTextStore->Destroy();
+  }
+  return NS_OK;
+}
+
+nsresult
+nsTextStore::OnTextChangeInternal(PRUint32 aStart,
+                                  PRUint32 aOldEnd,
+                                  PRUint32 aNewEnd)
+{
+  if (!mLock && mSink && 0 != (mSinkMask & TS_AS_TEXT_CHANGE)) {
+    mTextChange.acpStart = PR_MIN(mTextChange.acpStart, LONG(aStart));
+    mTextChange.acpOldEnd = PR_MAX(mTextChange.acpOldEnd, LONG(aOldEnd));
+    mTextChange.acpNewEnd = PR_MAX(mTextChange.acpNewEnd, LONG(aNewEnd));
+    ::PostMessageW(mWindow->GetWindowHandle(),
+                   WM_USER_TSF_TEXTCHANGE, 0, NULL);
+  }
+  return NS_OK;
+}
+
+void
+nsTextStore::OnTextChangeMsgInternal(void)
+{
+  if (!mLock && mSink && 0 != (mSinkMask & TS_AS_TEXT_CHANGE) &&
+      PR_INT32_MAX > mTextChange.acpStart) {
+    mSink->OnTextChange(0, &mTextChange);
+    mTextChange.acpStart = PR_INT32_MAX;
+    mTextChange.acpOldEnd = mTextChange.acpNewEnd = 0;
+  }
+}
+
+nsresult
+nsTextStore::OnSelectionChangeInternal(void)
+{
+  if (!mLock && mSink && 0 != (mSinkMask & TS_AS_SEL_CHANGE)) {
+    mSink->OnSelectionChange();
+  }
+  return NS_OK;
+}
+
+void
+nsTextStore::CommitCompositionInternal(PRBool aDiscard)
+{
+  if (mCompositionView && aDiscard) {
+    mCompositionString.Truncate(0);
+    if (mSink && !mLock) {
+      TS_TEXTCHANGE textChange;
+      textChange.acpStart = mCompositionStart;
+      textChange.acpOldEnd = mCompositionStart + mCompositionLength;
+      textChange.acpNewEnd = mCompositionStart;
+      mSink->OnTextChange(0, &textChange);
+    }
+  }
+  // Terminate two contexts, the base context (mContext) and the top
+  // if the top context is not the same as the base context
+  nsRefPtr<ITfContext> context = mContext;
+  do {
+    if (context) {
+      nsRefPtr<ITfContextOwnerCompositionServices> services;
+      context->QueryInterface(IID_ITfContextOwnerCompositionServices,
+                              getter_AddRefs(services));
+      if (services)
+        services->TerminateComposition(NULL);
+    }
+    if (context != mContext)
+      break;
+    if (mDocumentMgr)
+      mDocumentMgr->GetTop(getter_AddRefs(context));
+  } while (context != mContext);
+}
+
+static
+PRBool
+GetCompartment(IUnknown* pUnk,
+               const GUID& aID,
+               ITfCompartment** aCompartment)
+{
+  if (!pUnk) return PR_FALSE;
+
+  nsRefPtr<ITfCompartmentMgr> compMgr;
+  pUnk->QueryInterface(IID_ITfCompartmentMgr, getter_AddRefs(compMgr));
+  if (!compMgr) return PR_FALSE;
+
+  return SUCCEEDED(compMgr->GetCompartment(aID, aCompartment)) &&
+         (*aCompartment) != NULL;
+}
+
+void
+nsTextStore::SetIMEOpenState(PRBool aState)
+{
+  PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+         ("TSF: SetIMEOpenState, state=%lu\n", aState));
+
+  nsRefPtr<ITfCompartment> comp;
+  if (!GetCompartment(sTsfThreadMgr,
+                      GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
+                      getter_AddRefs(comp)))
+    return;
+
+  VARIANT variant;
+  variant.vt = VT_I4;
+  variant.lVal = aState;
+  comp->SetValue(sTsfClientId, &variant);
+}
+
+PRBool
+nsTextStore::GetIMEOpenState(void)
+{
+  nsRefPtr<ITfCompartment> comp;
+  if (!GetCompartment(sTsfThreadMgr,
+                      GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
+                      getter_AddRefs(comp)))
+    return PR_FALSE;
+
+  VARIANT variant;
+  ::VariantInit(&variant);
+  if (SUCCEEDED(comp->GetValue(&variant)) && variant.vt == VT_I4)
+    return variant.lVal != 0;
+
+  ::VariantClear(&variant); // clear up in case variant.vt != VT_I4
+  return PR_FALSE;
+}
+
+void
+nsTextStore::SetIMEEnabledInternal(PRUint32 aState)
+{
+  PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+         ("TSF: SetIMEEnabled, state=%lu\n", aState));
+
+  VARIANT variant;
+  variant.vt = VT_I4;
+  variant.lVal = aState != nsIWidget::IME_STATUS_ENABLED;
+
+  // Set two contexts, the base context (mContext) and the top
+  // if the top context is not the same as the base context
+  nsRefPtr<ITfContext> context = mContext;
+  nsRefPtr<ITfCompartment> comp;
+  do {
+    if (GetCompartment(context, GUID_COMPARTMENT_KEYBOARD_DISABLED,
+                       getter_AddRefs(comp)))
+      comp->SetValue(sTsfClientId, &variant);
+
+    if (context != mContext)
+      break;
+    if (mDocumentMgr)
+      mDocumentMgr->GetTop(getter_AddRefs(context));
+  } while (context != mContext);
+}
+
+HRESULT
+nsTextStore::LoadManagers(void)
+{
+  HRESULT hr;
+  if (!mDAMgr) {
+    hr = ::CoCreateInstance(CLSID_TF_DisplayAttributeMgr, NULL,
+                            CLSCTX_INPROC_SERVER, IID_ITfDisplayAttributeMgr,
+                            getter_AddRefs(mDAMgr));
+    NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+  }
+  if (!mCatMgr) {
+    hr = ::CoCreateInstance(CLSID_TF_CategoryMgr, NULL, CLSCTX_INPROC_SERVER,
+                            IID_ITfCategoryMgr, getter_AddRefs(mCatMgr));
+    NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+  }
+  return S_OK;
+}
+
+void
+nsTextStore::Initialize(void)
+{
+#ifdef PR_LOGGING
+  if (!sTextStoreLog)
+    sTextStoreLog = PR_NewLogModule("nsTextStoreWidgets");
+#endif
+  if (!sTsfThreadMgr) {
+    PRBool enableTsf = PR_TRUE;
+    nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+    if (prefs) {
+      nsCOMPtr<nsIPrefBranch> prefBranch;
+      prefs->GetBranch(nsnull, getter_AddRefs(prefBranch));
+      if (prefBranch && NS_FAILED(prefBranch->GetBoolPref(
+            "intl.enable_tsf_support", &enableTsf)))
+        enableTsf = PR_TRUE;
+    }
+    if (enableTsf) {
+      if (SUCCEEDED(CoCreateInstance(CLSID_TF_ThreadMgr, NULL,
+            CLSCTX_INPROC_SERVER, IID_ITfThreadMgr,
+            reinterpret_cast<void**>(&sTsfThreadMgr)))) {
+        if (FAILED(sTsfThreadMgr->Activate(&sTsfClientId))) {
+          NS_RELEASE(sTsfThreadMgr);
+          NS_WARNING("failed to activate TSF\n");
+        }
+      } else
+        // TSF not installed?
+        NS_WARNING("failed to create TSF manager\n");
+    }
+  }
+  if (sTsfThreadMgr && !sTsfTextStore) {
+    sTsfTextStore = new nsTextStore();
+    if (!sTsfTextStore)
+      NS_ERROR("failed to create text store\n");
+  }
+  if (sTsfThreadMgr && !sFlushTIPInputMessage) {
+    sFlushTIPInputMessage = ::RegisterWindowMessageW(
+        NS_LITERAL_STRING("Flush TIP Input Message").get());
+  }
+}
+
+void
+nsTextStore::Terminate(void)
+{
+  NS_IF_RELEASE(sTsfTextStore);
+  if (sTsfThreadMgr) {
+    sTsfThreadMgr->Deactivate();
+    NS_RELEASE(sTsfThreadMgr);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/widget/src/windows/nsTextStore.h
@@ -0,0 +1,233 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ningjie Chen <chenn@email.uc.edu>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef NSTEXTSTORE_H_
+#define NSTEXTSTORE_H_
+
+#include "nsAutoPtr.h"
+#include "nsString.h"
+
+#include <msctf.h>
+#include <textstor.h>
+
+struct ITfThreadMgr;
+struct ITfDocumentMgr;
+class nsWindow;
+
+// It doesn't work well when we notify TSF of text change
+// during a mutation observer call because things get broken.
+// So we post a message and notify TSF when we get it later.
+#define WM_USER_TSF_TEXTCHANGE  (WM_USER + 0x100)
+
+/*
+ * Text Services Framework text store
+ */
+
+class nsTextStore : public ITextStoreACP,
+                    public ITfContextOwnerCompositionSink
+{
+public: /*IUnknown*/
+  STDMETHODIMP_(ULONG)  AddRef(void);
+  STDMETHODIMP          QueryInterface(REFIID, void**);
+  STDMETHODIMP_(ULONG)  Release(void);
+
+public: /*ITextStoreACP*/
+  STDMETHODIMP AdviseSink(REFIID, IUnknown*, DWORD);
+  STDMETHODIMP UnadviseSink(IUnknown*);
+  STDMETHODIMP RequestLock(DWORD, HRESULT*);
+  STDMETHODIMP GetStatus(TS_STATUS*);
+  STDMETHODIMP QueryInsert(LONG, LONG, ULONG, LONG*, LONG*);
+  STDMETHODIMP GetSelection(ULONG, ULONG, TS_SELECTION_ACP*, ULONG*);
+  STDMETHODIMP SetSelection(ULONG, const TS_SELECTION_ACP*);
+  STDMETHODIMP GetText(LONG, LONG, WCHAR*, ULONG, ULONG*, TS_RUNINFO*, ULONG,
+                       ULONG*, LONG*);
+  STDMETHODIMP SetText(DWORD, LONG, LONG, const WCHAR*, ULONG, TS_TEXTCHANGE*);
+  STDMETHODIMP GetFormattedText(LONG, LONG, IDataObject**);
+  STDMETHODIMP GetEmbedded(LONG, REFGUID, REFIID, IUnknown**);
+  STDMETHODIMP QueryInsertEmbedded(const GUID*, const FORMATETC*, BOOL*);
+  STDMETHODIMP InsertEmbedded(DWORD, LONG, LONG, IDataObject*, TS_TEXTCHANGE*);
+  STDMETHODIMP RequestSupportedAttrs(DWORD, ULONG, const TS_ATTRID*);
+  STDMETHODIMP RequestAttrsAtPosition(LONG, ULONG, const TS_ATTRID*, DWORD);
+  STDMETHODIMP RequestAttrsTransitioningAtPosition(LONG, ULONG,
+                                                   const TS_ATTRID*, DWORD);
+  STDMETHODIMP FindNextAttrTransition(LONG, LONG, ULONG, const TS_ATTRID*,
+                                      DWORD, LONG*, BOOL*, LONG*);
+  STDMETHODIMP RetrieveRequestedAttrs(ULONG, TS_ATTRVAL*, ULONG*);
+  STDMETHODIMP GetEndACP(LONG*);
+  STDMETHODIMP GetActiveView(TsViewCookie*);
+  STDMETHODIMP GetACPFromPoint(TsViewCookie, const POINT*, DWORD, LONG*);
+  STDMETHODIMP GetTextExt(TsViewCookie, LONG, LONG, RECT*, BOOL*);
+  STDMETHODIMP GetScreenExt(TsViewCookie, RECT*);
+  STDMETHODIMP GetWnd(TsViewCookie, HWND*);
+  STDMETHODIMP InsertTextAtSelection(DWORD, const WCHAR*, ULONG, LONG*, LONG*,
+                                     TS_TEXTCHANGE*);
+  STDMETHODIMP InsertEmbeddedAtSelection(DWORD, IDataObject*, LONG*, LONG*,
+                                         TS_TEXTCHANGE*);
+
+public: /*ITfContextOwnerCompositionSink*/
+  STDMETHODIMP OnStartComposition(ITfCompositionView*, BOOL*);
+  STDMETHODIMP OnUpdateComposition(ITfCompositionView*, ITfRange*);
+  STDMETHODIMP OnEndComposition(ITfCompositionView*);
+
+public:
+  static void     Initialize(void);
+  static void     Terminate(void);
+  static void     SetIMEOpenState(PRBool);
+  static PRBool   GetIMEOpenState(void);
+
+  static void     CommitComposition(PRBool aDiscard)
+  {
+    if (!sTsfTextStore) return;
+    sTsfTextStore->CommitCompositionInternal(aDiscard);
+  }
+
+  static void     SetIMEEnabled(PRUint32 aState)
+  {
+    if (!sTsfTextStore) return;
+    sTsfTextStore->SetIMEEnabledInternal(aState);
+  }
+
+  static nsresult OnFocusChange(PRBool, nsWindow*, PRUint32);
+
+  static nsresult OnTextChange(PRUint32 aStart,
+                               PRUint32 aOldEnd,
+                               PRUint32 aNewEnd)
+  {
+    if (!sTsfTextStore) return NS_OK;
+    return sTsfTextStore->OnTextChangeInternal(aStart, aOldEnd, aNewEnd);
+  }
+
+  static void     OnTextChangeMsg(void)
+  {
+    if (!sTsfTextStore) return;
+    // Notify TSF text change
+    // (see comments on WM_USER_TSF_TEXTCHANGE in nsTextStore.h)
+    sTsfTextStore->OnTextChangeMsgInternal();
+  }
+
+  static nsresult OnSelectionChange(void)
+  {
+    if (!sTsfTextStore) return NS_OK;
+    return sTsfTextStore->OnSelectionChangeInternal();
+  }
+
+  static void*    GetNativeData(void)
+  {
+    // Returns the address of the pointer so that the TSF automatic test can
+    // replace the system object with a custom implementation for testing.
+    Initialize(); // Apply any previous changes
+    return (void*) & sTsfThreadMgr;
+  }
+
+protected:
+  nsTextStore();
+  ~nsTextStore();
+
+  PRBool   Create(nsWindow*, PRUint32);
+  PRBool   Destroy(void);
+  PRBool   Focus(void);
+  PRBool   Blur(void);
+
+  HRESULT  LoadManagers(void);
+  HRESULT  SetSelectionInternal(const TS_SELECTION_ACP*);
+  HRESULT  OnStartCompositionInternal(ITfCompositionView*, ITfRange*, PRBool);
+  void     CommitCompositionInternal(PRBool);
+  void     SetIMEEnabledInternal(PRUint32 aState);
+  nsresult OnTextChangeInternal(PRUint32, PRUint32, PRUint32);
+  void     OnTextChangeMsgInternal(void);
+  nsresult OnSelectionChangeInternal(void);
+
+  // TSF display attribute manager, loaded by LoadManagers
+  nsRefPtr<ITfDisplayAttributeMgr> mDAMgr;
+  // TSF category manager, loaded by LoadManagers
+  nsRefPtr<ITfCategoryMgr>         mCatMgr;
+
+  // Document manager for the currently focused editor
+  nsRefPtr<ITfDocumentMgr>     mDocumentMgr;
+  // Edit cookie associated with the current editing context
+  DWORD                        mEditCookie;
+  // Editing context at the bottom of mDocumentMgr's context stack
+  nsRefPtr<ITfContext>         mContext;
+  // Currently installed notification sink
+  nsRefPtr<ITextStoreACPSink>  mSink;
+  // TS_AS_* mask of what events to notify
+  DWORD                        mSinkMask;
+  // Window containing the focused editor
+  nsWindow*                    mWindow;
+  // 0 if not locked, otherwise TS_LF_* indicating the current lock
+  DWORD                        mLock;
+  // 0 if no lock is queued, otherwise TS_LF_* indicating the queue lock
+  DWORD                        mLockQueued;
+  // Cumulative text change offsets since the last notification
+  TS_TEXTCHANGE                mTextChange;
+  // NULL if no composition is active, otherwise the current composition
+  nsRefPtr<ITfCompositionView> mCompositionView;
+  // Current copy of the active composition string. Only mCompositionString is
+  // changed during a InsertTextAtSelection call if we have a composition.
+  // mCompositionString acts as a buffer until OnUpdateComposition is called
+  // and mCompositionString is flushed to editor through NS_TEXT_TEXT. This
+  // way all changes are updated in batches to avoid inconsistencies/artifacts.
+  nsString                     mCompositionString;
+  // "Current selection" during a composition, in ACP offsets.
+  // We use a fake selection during a composition because editor code doesn't
+  // like us accessing the actual selection during a composition. So we leave
+  // the actual selection alone and get/set mCompositionSelection instead
+  // during GetSelection/SetSelection calls.
+  TS_SELECTION_ACP             mCompositionSelection;
+  // The start and length of the current active composition, in ACP offsets
+  LONG                         mCompositionStart;
+  LONG                         mCompositionLength;
+
+  // TSF thread manager object for the current application
+  static ITfThreadMgr*  sTsfThreadMgr;
+  // TSF client ID for the current application
+  static DWORD          sTsfClientId;
+  // Current text store. Currently only ONE nsTextStore instance is ever used,
+  // although Create is called when an editor is focused and Destroy called
+  // when the focused editor is blurred.
+  static nsTextStore*   sTsfTextStore;
+
+  // Message the Tablet Input Panel uses to flush text during blurring.
+  // See comments in Destroy
+  static UINT           sFlushTIPInputMessage;
+
+private:
+  ULONG                       mRefCnt;
+};
+
+#endif /*NSTEXTSTORE_H_*/
--- a/widget/src/windows/nsWindow.cpp
+++ b/widget/src/windows/nsWindow.cpp
@@ -29,16 +29,17 @@
  *   Pierre Phaneuf <pp@ludusdesign.com>
  *   Robert O'Callahan <roc+moz@cs.cmu.edu>
  *   Roy Yokoyama <yokoyama@netscape.com>
  *   Makoto Kato  <m_kato@ga2.so-net.ne.jp>
  *   Masayuki Nakano <masayuki@d-toybox.com>
  *   Dainis Jonitis <Dainis_Jonitis@swh-t.lv>
  *   Christian Biesinger <cbiesinger@web.de>
  *   Mats Palmgren <mats.palmgren@bredband.net>
+ *   Ningjie Chen <chenn@email.uc.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -145,16 +146,19 @@
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsXPIDLString.h"
 #include "nsIFile.h"
 
 #include "prprf.h"
 #include "prmem.h"
 
 
+#ifdef NS_ENABLE_TSF
+#include "nsTextStore.h"
+#endif //NS_ENABLE_TSF
 
 #ifdef WINCE
 
 static PRBool gSoftKeyMenuBar = PR_FALSE;
 
 static void CreateSoftKeyMenuBar(HWND wnd)
 {
   if (!wnd)
@@ -741,24 +745,29 @@ nsWindow::nsWindow() : nsBaseWidget()
     // Heap dump
     nsWindow::uWM_HEAP_DUMP = ::RegisterWindowMessageW(kMozHeapDumpMessageString);
   }
 
   mNativeDragTarget = nsnull;
   mIsTopWidgetWindow = PR_FALSE;
   mLastKeyboardLayout = 0;
 
+#ifdef NS_ENABLE_TSF
+  if (!sInstanceCount)
+    nsTextStore::Initialize();
+#endif //NS_ENABLE_TSF
+
 #ifndef WINCE
   if (!sInstanceCount && SUCCEEDED(::OleInitialize(NULL))) {
     sIsOleInitialized = TRUE;
   }
   NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n");
+#endif
 
   sInstanceCount++;
-#endif
 }
 
 //-------------------------------------------------------------------------
 //
 // nsWindow destructor
 //
 //-------------------------------------------------------------------------
 nsWindow::~nsWindow()
@@ -783,21 +792,27 @@ nsWindow::~nsWindow()
     Destroy();
   }
 
   if (mCursor == -1) {
     // A successfull SetCursor call will destroy the custom cursor, if it's ours
     SetCursor(eCursor_standard);
   }
 
+  sInstanceCount--;
+
+#ifdef NS_ENABLE_TSF
+  if (!sInstanceCount)
+    nsTextStore::Terminate();
+#endif //NS_ENABLE_TSF
+
 #ifndef WINCE
   //
   // delete any of the IME structures that we allocated
   //
-  sInstanceCount--;
   if (sInstanceCount == 0) {
     if (sIMECompUnicode) 
       delete sIMECompUnicode;
     if (sIMEAttributeArray) 
       delete [] sIMEAttributeArray;
     if (sIMECompClauseArray) 
       delete [] sIMECompClauseArray;
 
@@ -2821,16 +2836,22 @@ void* nsWindow::GetNativeData(PRUint32 a
     case NS_NATIVE_GRAPHIC:
       // XXX:  This is sleezy!!  Remember to Release the DC after using it!
 #ifdef MOZ_XUL
       return (void*)(eTransparencyTransparent == mTransparencyMode) ?
         mMemoryDC : ::GetDC(mWnd);
 #else
       return (void*)::GetDC(mWnd);
 #endif
+
+#ifdef NS_ENABLE_TSF
+    case NS_NATIVE_TSF_POINTER:
+      return nsTextStore::GetNativeData();
+#endif //NS_ENABLE_TSF
+
     case NS_NATIVE_COLORMAP:
     default:
       break;
   }
 
   return NULL;
 }
 
@@ -5298,16 +5319,22 @@ PRBool nsWindow::ProcessMessage(UINT msg
       else if (msg == nsWindow::uWM_HEAP_DUMP) {
         // XXX for now we use c:\heapdump.txt until we figure out how to
         // XXX pass in message parameters.
         HeapDump("c:\\heapdump.txt", "whatever");
         result = PR_TRUE;
       }
 #endif // WINCE
 
+#ifdef NS_ENABLE_TSF
+      else if (msg == WM_USER_TSF_TEXTCHANGE) {
+        nsTextStore::OnTextChangeMsg();
+      }
+#endif //NS_ENABLE_TSF
+
     }
     break;
 #ifndef WINCE
   case WM_DWMCOMPOSITIONCHANGED:
     BroadcastMsg(mWnd, WM_DWMCOMPOSITIONCHANGED);
     DispatchStandardEvent(NS_THEMECHANGED);
     if (nsUXThemeData::CheckForCompositor() && mTransparencyMode == eTransparencyGlass) {
       MARGINS margins = { -1, -1, -1, -1 };
@@ -7422,18 +7449,18 @@ PRBool nsWindow::OnIMEQueryCharPosition(
   if (!selection.mSucceeded)
     return PR_FALSE;
 
   PRUint32 offset = selection.mReply.mOffset + pCharPosition->dwCharPos;
   PRBool useCaretRect = selection.mReply.mString.IsEmpty();
 
   nsIntRect r;
   if (!useCaretRect) {
-    nsQueryContentEvent charRect(PR_TRUE, NS_QUERY_CHARACTER_RECT, this);
-    charRect.InitForQueryCharacterRect(offset);
+    nsQueryContentEvent charRect(PR_TRUE, NS_QUERY_TEXT_RECT, this);
+    charRect.InitForQueryTextRect(offset, 1);
     InitEvent(charRect, &point);
     DispatchWindowEvent(&charRect);
     if (charRect.mSucceeded)
       r = charRect.mReply.mRect;
     else
       useCaretRect = PR_TRUE;
   }
 
@@ -7531,16 +7558,21 @@ BOOL nsWindow::OnIMEStartComposition()
 }
 
 //==========================================================================
 NS_IMETHODIMP nsWindow::ResetInputState()
 {
 #ifdef DEBUG_KBSTATE
   printf("ResetInputState\n");
 #endif
+
+#ifdef NS_ENABLE_TSF
+  nsTextStore::CommitComposition(PR_FALSE);
+#endif //NS_ENABLE_TSF
+
   HIMC hIMC = ::ImmGetContext(mWnd);
   if (hIMC) {
     BOOL ret = FALSE;
     ret = ::ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_COMPLETE, NULL);
     ret = ::ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, NULL);
     //NS_ASSERTION(ret, "ImmNotify failed");
     ::ImmReleaseContext(mWnd, hIMC);
   }
@@ -7548,16 +7580,21 @@ NS_IMETHODIMP nsWindow::ResetInputState(
 }
 
 //==========================================================================
 NS_IMETHODIMP nsWindow::SetIMEOpenState(PRBool aState)
 {
 #ifdef DEBUG_KBSTATE
   printf("SetIMEOpenState %s\n", (aState ? "Open" : "Close"));
 #endif 
+
+#ifdef NS_ENABLE_TSF
+  nsTextStore::SetIMEOpenState(aState);
+#endif //NS_ENABLE_TSF
+
   HIMC hIMC = ::ImmGetContext(mWnd);
   if (hIMC) {
     ::ImmSetOpenStatus(hIMC, aState ? TRUE : FALSE);
     ::ImmReleaseContext(mWnd, hIMC);
   }
   return NS_OK;
 }
 
@@ -7566,22 +7603,31 @@ NS_IMETHODIMP nsWindow::GetIMEOpenState(
 {
   HIMC hIMC = ::ImmGetContext(mWnd);
   if (hIMC) {
     BOOL isOpen = ::ImmGetOpenStatus(hIMC);
     *aState = isOpen ? PR_TRUE : PR_FALSE;
     ::ImmReleaseContext(mWnd, hIMC);
   } else 
     *aState = PR_FALSE;
+
+#ifdef NS_ENABLE_TSF
+  *aState |= nsTextStore::GetIMEOpenState();
+#endif //NS_ENABLE_TSF
+
   return NS_OK;
 }
 
 //==========================================================================
 NS_IMETHODIMP nsWindow::SetIMEEnabled(PRUint32 aState)
 {
+#ifdef NS_ENABLE_TSF
+  nsTextStore::SetIMEEnabled(aState);
+#endif //NS_ENABLE_TSF
+
   if (sIMEIsComposing)
     ResetInputState();
   mIMEEnabled = aState;
   PRBool enable = (aState == nsIWidget::IME_STATUS_ENABLED ||
                    aState == nsIWidget::IME_STATUS_PLUGIN);
   if (!enable != !mOldIMC)
     return NS_OK;
   mOldIMC = ::ImmAssociateContext(mWnd, enable ? mOldIMC : NULL);
@@ -7598,16 +7644,21 @@ NS_IMETHODIMP nsWindow::GetIMEEnabled(PR
 }
 
 //==========================================================================
 NS_IMETHODIMP nsWindow::CancelIMEComposition()
 {
 #ifdef DEBUG_KBSTATE
   printf("CancelIMEComposition\n");
 #endif 
+
+#ifdef NS_ENABLE_TSF
+  nsTextStore::CommitComposition(PR_TRUE);
+#endif //NS_ENABLE_TSF
+
   HIMC hIMC = ::ImmGetContext(mWnd);
   if (hIMC) {
     BOOL ret = FALSE;
     ret = ::ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, NULL);
     ::ImmReleaseContext(mWnd, hIMC);
   }
   return NS_OK;
 }
@@ -7803,16 +7854,40 @@ static VOID CALLBACK nsGetAttentionTimer
     }
 
     gAttentionTimerMonitor->SetFlashed(hwnd);
   }
   else
     gAttentionTimerMonitor->KillTimer(hwnd);
 }
 
+
+#ifdef NS_ENABLE_TSF
+NS_IMETHODIMP
+nsWindow::OnIMEFocusChange(PRBool aFocus)
+{
+  return nsTextStore::OnFocusChange(aFocus, this, mIMEEnabled);
+}
+
+NS_IMETHODIMP
+nsWindow::OnIMETextChange(PRUint32 aStart,
+                          PRUint32 aOldEnd,
+                          PRUint32 aNewEnd)
+{
+  return nsTextStore::OnTextChange(aStart, aOldEnd, aNewEnd);
+}
+
+NS_IMETHODIMP
+nsWindow::OnIMESelectionChange(void)
+{
+  return nsTextStore::OnSelectionChange();
+}
+#endif //NS_ENABLE_TSF
+
+
 // Draw user's attention to this window until it comes to foreground.
 NS_IMETHODIMP
 nsWindow::GetAttention(PRInt32 aCycleCount)
 {
   // Got window?
   if (!mWnd)
     return NS_ERROR_NOT_INITIALIZED;
 
--- a/widget/src/windows/nsWindow.h
+++ b/widget/src/windows/nsWindow.h
@@ -20,16 +20,17 @@
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Robert O'Callahan <roc+moz@cs.cmu.edu>
  *   Dean Tessman <dean_tessman@hotmail.com>
  *   Makoto Kato  <m_kato@ga2.so-net.ne.jp>
  *   Dainis Jonitis <Dainis_Jonitis@swh-t.lv>
  *   Masayuki Nakano <masayuki@d-toybox.com>
+ *   Ningjie Chen <chenn@email.uc.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -68,16 +69,21 @@ struct nsFakeCharMessage;
 
 #ifdef ACCESSIBILITY
 #include "OLEACC.H"
 #include "nsIAccessible.h"
 #endif
 
 #include "gfxWindowsSurface.h"
 
+// Text Services Framework support
+#ifndef WINCE
+#define NS_ENABLE_TSF
+#endif //WINCE
+
 #define IME_MAX_CHAR_POS       64
 
 #define NSRGB_2_COLOREF(color) \
             RGB(NS_GET_R(color),NS_GET_G(color),NS_GET_B(color))
 #define COLOREF_2_NSRGB(color) \
             NS_RGB(GetRValue(color), GetGValue(color), GetBValue(color))
 
 #define WIN2K_VERSION   0x500
@@ -230,31 +236,39 @@ public:
   NS_IMETHOD ResetInputState();
   NS_IMETHOD SetIMEOpenState(PRBool aState);
   NS_IMETHOD GetIMEOpenState(PRBool* aState);
   NS_IMETHOD SetIMEEnabled(PRUint32 aState);
   NS_IMETHOD GetIMEEnabled(PRUint32* aState);
   NS_IMETHOD CancelIMEComposition();
   NS_IMETHOD GetToggledKeyState(PRUint32 aKeyCode, PRBool* aLEDState);
 
+#ifdef NS_ENABLE_TSF
+  NS_IMETHOD OnIMEFocusChange(PRBool aFocus);
+  NS_IMETHOD OnIMETextChange(PRUint32 aStart, PRUint32 aOldEnd, PRUint32 aNewEnd);
+  NS_IMETHOD OnIMESelectionChange(void);
+#endif //NS_ENABLE_TSF
+
   PRBool IMEMouseHandling(PRInt32 aAction, LPARAM lParam);
   PRBool IMECompositionHitTest(POINT * ptPos);
   PRBool HandleMouseActionOfIME(PRInt32 aAction, POINT* ptPos);
   void GetCompositionWindowPos(HIMC hIMC, PRUint32 aEventType, COMPOSITIONFORM *cpForm);
 
   // nsSwitchToUIThread interface
   virtual BOOL            CallMethod(MethodInfo *info);
 
   HWND                    GetWindowHandle() { return mWnd; }
   WNDPROC                 GetPrevWindowProc() { return mPrevWndProc; }
 
   virtual PRBool          DispatchMouseEvent(PRUint32 aEventType, WPARAM wParam,
                                              LPARAM lParam,
                                              PRBool aIsContextMenuKey = PR_FALSE,
                                              PRInt16 aButton = nsMouseEvent::eLeftButton);
+  virtual PRBool          DispatchWindowEvent(nsGUIEvent* event);
+  virtual PRBool          DispatchWindowEvent(nsGUIEvent*event, nsEventStatus &aStatus);
 #ifdef ACCESSIBILITY
   virtual PRBool          DispatchAccessibleEvent(PRUint32 aEventType, nsIAccessible** aAccessible, nsIntPoint* aPoint = nsnull);
   already_AddRefed<nsIAccessible> GetRootAccessible();
 #endif
   virtual PRBool          AutoErase();
   nsIntPoint*             GetLastPoint() { return &mLastPoint; }
 
   PRInt32                 GetNewCmdMenuId() { mMenuCmdId++; return mMenuCmdId; }
@@ -298,18 +312,16 @@ protected:
 
   LRESULT                 ProcessCharMessage(const MSG &aMsg,
                                              PRBool *aEventDispatched);
   LRESULT                 ProcessKeyUpMessage(const MSG &aMsg,
                                               PRBool *aEventDispatched);
   LRESULT                 ProcessKeyDownMessage(const MSG &aMsg,
                                                 PRBool *aEventDispatched);
 
-  virtual PRBool          DispatchWindowEvent(nsGUIEvent* event);
-  virtual PRBool          DispatchWindowEvent(nsGUIEvent*event, nsEventStatus &aStatus);
 
    // Allow Derived classes to modify the height that is passed
    // when the window is created or resized.
   virtual PRInt32         GetHeight(PRInt32 aProposedHeight);
   virtual LPCWSTR         WindowClass();
   virtual LPCWSTR         WindowPopupClass();
   virtual DWORD           WindowStyle();
   virtual DWORD           WindowExStyle();
--- a/widget/src/xpwidgets/nsBaseWidget.h
+++ b/widget/src/xpwidgets/nsBaseWidget.h
@@ -134,23 +134,26 @@ public:
   NS_IMETHOD              EndSecureKeyboardInput();
   NS_IMETHOD              SetWindowTitlebarColor(nscolor aColor, PRBool aActive);
   virtual PRBool          ShowsResizeIndicator(nsIntRect* aResizerRect);
   virtual void            ConvertToDeviceCoordinates(nscoord  &aX,nscoord &aY) {}
   virtual void            FreeNativeData(void * data, PRUint32 aDataType) {}
   NS_IMETHOD              BeginResizeDrag(nsGUIEvent* aEvent, PRInt32 aHorizontal, PRInt32 aVertical);
   virtual nsresult        ActivateNativeMenuItemAt(const nsAString& indexString) { return NS_ERROR_NOT_IMPLEMENTED; }
   virtual nsresult        ForceUpdateNativeMenuAt(const nsAString& indexString) { return NS_ERROR_NOT_IMPLEMENTED; }
-  NS_IMETHOD              ResetInputState() { return NS_ERROR_NOT_IMPLEMENTED; }
+  NS_IMETHOD              ResetInputState() { return NS_OK; }
   NS_IMETHOD              SetIMEOpenState(PRBool aState) { return NS_ERROR_NOT_IMPLEMENTED; }
   NS_IMETHOD              GetIMEOpenState(PRBool* aState) { return NS_ERROR_NOT_IMPLEMENTED; }
   NS_IMETHOD              SetIMEEnabled(PRUint32 aState) { return NS_ERROR_NOT_IMPLEMENTED; }
   NS_IMETHOD              GetIMEEnabled(PRUint32* aState) { return NS_ERROR_NOT_IMPLEMENTED; }
-  NS_IMETHOD              CancelIMEComposition() { return NS_ERROR_NOT_IMPLEMENTED; }
+  NS_IMETHOD              CancelIMEComposition() { return NS_OK; }
   NS_IMETHOD              GetToggledKeyState(PRUint32 aKeyCode, PRBool* aLEDState) { return NS_ERROR_NOT_IMPLEMENTED; }
+  NS_IMETHOD              OnIMEFocusChange(PRBool aFocus) { return NS_ERROR_NOT_IMPLEMENTED; }
+  NS_IMETHOD              OnIMETextChange(PRUint32 aStart, PRUint32 aOldEnd, PRUint32 aNewEnd) { return NS_ERROR_NOT_IMPLEMENTED; }
+  NS_IMETHOD              OnIMESelectionChange(void) { return NS_ERROR_NOT_IMPLEMENTED; }
 
 protected:
 
   virtual void            ResolveIconName(const nsAString &aIconName,
                                           const nsAString &aIconSuffix,
                                           nsILocalFile **aResult);
   virtual void            OnDestroy();
   virtual void            BaseCreate(nsIWidget *aParent,
--- a/widget/tests/Makefile.in
+++ b/widget/tests/Makefile.in
@@ -34,19 +34,32 @@
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
 DEPTH		= ../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
-relativesrcdir  = widget/test 
+relativesrcdir  = widget/tests
 
 include $(DEPTH)/config/autoconf.mk
+
+ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
+ifneq ($(OS_ARCH), WINCE)
+CPP_UNIT_TESTS += TestWinTSF.cpp  \
+                  $(NULL)
+REQUIRES += appshell content docshell \
+            dom embed_base gfx layout locale \
+            necko string thebes uriloader view \
+            webbrwsr widget xpcom \
+            $(NULL)
+endif
+endif
+
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES =	test_bug343416.xul \
 		test_bug444800.xul \
 		test_bug462106.xul \
 		test_keycodes.xul \
 		$(NULL)
 
new file mode 100644
--- /dev/null
+++ b/widget/tests/TestWinTSF.cpp
@@ -0,0 +1,1830 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ningjie Chen <chenn@email.uc.edu>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+/* This tests Mozilla's Text Services Framework implementation (bug #88831)
+ *
+ * The Mozilla implementation interacts with the TSF system through a
+ * system-provided COM interface, ITfThreadMgr. This tests works by swapping
+ * the system version of the interface with a custom version implemented in
+ * here. This way the Mozilla implementation thinks it's interacting with the
+ * system but in fact is interacting with this test program. This allows the
+ * test program to access and test every aspect of the Mozilla implementation.
+ */
+
+#include <msctf.h>
+#include <textstor.h>
+
+#include "TestHarness.h"
+
+#define WM_USER_TSF_TEXTCHANGE  (WM_USER + 0x100)
+
+#ifndef MOZILLA_INTERNAL_API
+// some of the includes make use of internal string types
+#define nsAString_h___
+#define nsString_h___
+class nsAFlatString;
+class nsAFlatCString;
+#endif
+
+#include "nsWeakReference.h"
+#include "nsIAppShell.h"
+#include "nsWidgetsCID.h"
+#include "nsIAppShellService.h"
+#include "nsAppShellCID.h"
+#include "nsNetUtil.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIXULWindow.h"
+#include "nsIBaseWindow.h"
+#include "nsIDOMWindowInternal.h"
+#include "nsIDocShell.h"
+#include "nsIWidget.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsIFrame.h"
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsIDOMHTMLBodyElement.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsIDOMHTMLTextAreaElement.h"
+#include "nsISelectionController.h"
+#include "nsIViewManager.h"
+
+#ifndef MOZILLA_INTERNAL_API
+#undef nsString_h___
+#undef nsAString_h___
+#endif
+
+static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
+
+class TSFImpl;
+
+class TestApp : public nsIWebProgressListener, public nsSupportsWeakReference
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIWEBPROGRESSLISTENER
+
+  TestApp() : mFailed(PR_FALSE) {}
+  ~TestApp() {}
+
+  nsresult Run(void);
+  PRBool CheckFailed(void);
+
+  typedef PRBool (TestApp::*test_type)(void);
+
+protected:
+  nsresult Init(void);
+  nsresult Term(void);
+  PRBool RunTest(test_type aTest, PRBool aLock = PR_TRUE);
+
+  PRBool TestFocus(void);
+  PRBool TestClustering(void);
+  PRBool TestSelection(void);
+  PRBool TestText(void);
+  PRBool TestExtents(void);
+  PRBool TestComposition(void);
+  PRBool TestNotification(void);
+
+  PRBool TestApp::TestSelectionInternal(char* aTestName,
+                                        LONG aStart,
+                                        LONG aEnd,
+                                        TsActiveSelEnd aSelEnd);
+  PRBool TestCompositionSelectionAndText(char* aTestName,
+                                         LONG aExpectedSelStart,
+                                         LONG aExpectedSelEnd,
+                                         nsString& aReferenceString);
+  PRBool TestNotificationTextChange(nsIWidget* aWidget,
+                                    PRUint32 aCode,
+                                    const nsAString& aCharacter,
+                                    LONG aStart,
+                                    LONG aOldEnd,
+                                    LONG aNewEnd);
+  nsresult GetSelCon(nsISelectionController** aSelCon);
+
+  PRBool mFailed;
+  nsString mTestString;
+  nsRefPtr<TSFImpl> mImpl;
+  nsCOMPtr<nsIAppShell> mAppShell;
+  nsCOMPtr<nsIXULWindow> mWindow;
+  nsCOMPtr<nsIDOMNode> mCurrentNode;
+  nsCOMPtr<nsIDOMHTMLInputElement> mInput;
+  nsCOMPtr<nsIDOMHTMLTextAreaElement> mTextArea;
+  nsCOMPtr<nsIDOMHTMLInputElement> mButton;
+};
+
+NS_IMETHODIMP
+TestApp::OnProgressChange(nsIWebProgress *aWebProgress,
+                           nsIRequest *aRequest,
+                           PRInt32 aCurSelfProgress,
+                           PRInt32 aMaxSelfProgress,
+                           PRInt32 aCurTotalProgress,
+                           PRInt32 aMaxTotalProgress)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TestApp::OnLocationChange(nsIWebProgress *aWebProgress,
+                           nsIRequest *aRequest,
+                           nsIURI *aLocation)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TestApp::OnStatusChange(nsIWebProgress *aWebProgress,
+                         nsIRequest *aRequest,
+                         nsresult aStatus,
+                         const PRUnichar *aMessage)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TestApp::OnSecurityChange(nsIWebProgress *aWebProgress,
+                           nsIRequest *aRequest,
+                           PRUint32 aState)
+{
+  return NS_OK;
+}
+
+// Simple TSF manager implementation for testing
+// Most methods are not implemented, but the ones used by Mozilla are
+//
+// XXX Implement appropriate methods here as the Mozilla TSF code changes
+//
+class TSFImpl : public ITfThreadMgr, public ITfDocumentMgr, public ITfContext,
+                public ITfRangeACP, public ITfCompositionView,
+                public ITextStoreACPSink
+{
+private:
+  ULONG mRefCnt;
+  nsRefPtr<TestApp> mTestApp;
+
+public:
+  TestApp::test_type mTest;
+  TestApp::test_type mOnFocus;
+  TestApp::test_type mOnBlur;
+  nsRefPtr<ITextStoreACP> mStore;
+  PRBool mFocused;
+  PRBool mContextPushed;
+  PRBool mDeactivated;
+  PRUint32 mFocusCount;
+  PRUint32 mBlurCount;
+  PRUint32 mRangeStart;
+  PRUint32 mRangeLength;
+  PRBool mTextChanged;
+  PRBool mSelChanged;
+  TS_TEXTCHANGE mTextChangeData;
+
+public:
+  TSFImpl(TestApp* test) : mTestApp(test), mTest(nsnull),
+      mRefCnt(0), mFocused(PR_FALSE), mDeactivated(PR_FALSE),
+      mFocusCount(0), mBlurCount(0), mRangeStart(0), mRangeLength(0),
+      mContextPushed(PR_FALSE), mOnFocus(nsnull), mOnBlur(nsnull),
+      mTextChanged(PR_FALSE), mSelChanged(PR_FALSE)
+  {
+  }
+
+  ~TSFImpl()
+  {
+  }
+
+public: // IUnknown
+
+  STDMETHODIMP QueryInterface(REFIID riid, void** ppUnk)
+  {
+    *ppUnk = NULL;
+    if (IID_IUnknown == riid || IID_ITfThreadMgr == riid)
+      *ppUnk = static_cast<ITfThreadMgr*>(this);
+    else if (IID_ITfDocumentMgr == riid)
+      *ppUnk = static_cast<ITfDocumentMgr*>(this);
+    else if (IID_ITfContext == riid)
+      *ppUnk = static_cast<ITfContext*>(this);
+    else if (IID_ITfRange == riid || IID_ITfRangeACP == riid)
+      *ppUnk = static_cast<ITfRangeACP*>(this);
+    else if (IID_ITextStoreACPSink == riid)
+      *ppUnk = static_cast<ITextStoreACPSink*>(this);
+    if (*ppUnk)
+      AddRef();
+    return *ppUnk ? S_OK : E_NOINTERFACE;
+  }
+
+  STDMETHODIMP_(ULONG) AddRef(void)
+  {
+    return ++mRefCnt;
+  }
+
+  STDMETHODIMP_(ULONG) Release(void)
+  {
+    if (--mRefCnt) return mRefCnt;
+    delete this;
+    return 0;
+  }
+
+public: // ITfThreadMgr
+
+  STDMETHODIMP Activate(TfClientId *ptid)
+  {
+    *ptid = 1;
+    return S_OK;
+  }
+
+  STDMETHODIMP Deactivate(void)
+  {
+    SetFocus(NULL);
+    mDeactivated = PR_TRUE;
+    return S_OK;
+  }
+
+  STDMETHODIMP CreateDocumentMgr(ITfDocumentMgr **ppdim)
+  {
+    (*ppdim) = this;
+    (*ppdim)->AddRef();
+    return S_OK;
+  }
+
+  STDMETHODIMP EnumDocumentMgrs(IEnumTfDocumentMgrs **ppEnum)
+  {
+    NS_NOTREACHED("ITfThreadMgr::EnumDocumentMgrs");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP GetFocus(ITfDocumentMgr **ppdimFocus)
+  {
+    (*ppdimFocus) = mFocused ? this : NULL;
+    if (*ppdimFocus) (*ppdimFocus)->AddRef();
+    return S_OK;
+  }
+
+  STDMETHODIMP SetFocus(ITfDocumentMgr *pdimFocus)
+  {
+    mFocused = pdimFocus != NULL;
+    if (mFocused) {
+      ++mFocusCount;
+      if (mOnFocus) (mTestApp->*mOnFocus)();
+    } else {
+      ++mBlurCount;
+      if (mOnBlur) (mTestApp->*mOnBlur)();
+    }
+    return S_OK;
+  }
+
+  STDMETHODIMP AssociateFocus(HWND hwnd, ITfDocumentMgr *pdimNew,
+                           ITfDocumentMgr **ppdimPrev)
+  {
+    NS_NOTREACHED("ITfThreadMgr::AssociateFocus");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP IsThreadFocus(BOOL *pfThreadFocus)
+  {
+    *pfThreadFocus = TRUE;
+    return S_OK;
+  }
+
+  STDMETHODIMP GetFunctionProvider(REFCLSID clsid,
+                                ITfFunctionProvider **ppFuncProv)
+  {
+    NS_NOTREACHED("ITfThreadMgr::GetFunctionProvider");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP EnumFunctionProviders(IEnumTfFunctionProviders **ppEnum)
+  {
+    NS_NOTREACHED("ITfThreadMgr::EnumFunctionProviders");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP GetGlobalCompartment(ITfCompartmentMgr **ppCompMgr)
+  {
+    NS_NOTREACHED("ITfThreadMgr::GetGlobalCompartment");
+    return E_NOTIMPL;
+  }
+
+public: // ITfDocumentMgr
+
+  STDMETHODIMP CreateContext(TfClientId tidOwner, DWORD dwFlags,
+                             IUnknown *punk, ITfContext **ppic,
+                             TfEditCookie *pecTextStore)
+  {
+    punk->QueryInterface(IID_ITextStoreACP, getter_AddRefs(mStore));
+    NS_ENSURE_TRUE(mStore, E_FAIL);
+    HRESULT hr = mStore->AdviseSink(IID_ITextStoreACPSink,
+                                    static_cast<ITextStoreACPSink*>(this),
+                                    TS_AS_ALL_SINKS);
+    if (FAILED(hr)) mStore = NULL;
+    NS_ENSURE_TRUE(SUCCEEDED(hr), E_FAIL);
+    (*ppic) = this;
+    (*ppic)->AddRef();
+    *pecTextStore = 1;
+    return S_OK;
+  }
+
+  STDMETHODIMP Push(ITfContext *pic)
+  {
+    mContextPushed = PR_TRUE;
+    return S_OK;
+  }
+
+  STDMETHODIMP Pop(DWORD dwFlags)
+  {
+    if (!mStore || dwFlags != TF_POPF_ALL) return E_FAIL;
+    mStore->UnadviseSink(static_cast<ITextStoreACPSink*>(this));
+    mStore = NULL;
+    mContextPushed = PR_FALSE;
+    return S_OK;
+  }
+
+  STDMETHODIMP GetTop(ITfContext **ppic)
+  {
+    (*ppic) = mContextPushed ? this : NULL;
+    if (*ppic) (*ppic)->AddRef();
+    return S_OK;
+  }
+
+  STDMETHODIMP GetBase(ITfContext **ppic)
+  {
+    (*ppic) = mContextPushed ? this : NULL;
+    if (*ppic) (*ppic)->AddRef();
+    return S_OK;
+  }
+
+  STDMETHODIMP EnumContexts(IEnumTfContexts **ppEnum)
+  {
+    NS_NOTREACHED("ITfDocumentMgr::EnumContexts");
+    return E_NOTIMPL;
+  }
+
+public: // ITfContext
+
+  STDMETHODIMP RequestEditSession(TfClientId tid, ITfEditSession *pes,
+                                  DWORD dwFlags, HRESULT *phrSession)
+  {
+    NS_NOTREACHED("ITfContext::RequestEditSession");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP InWriteSession(TfClientId tid, BOOL *pfWriteSession)
+  {
+    NS_NOTREACHED("ITfContext::InWriteSession");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP GetSelection(TfEditCookie ec, ULONG ulIndex, ULONG ulCount,
+                            TF_SELECTION *pSelection, ULONG *pcFetched)
+  {
+    NS_NOTREACHED("ITfContext::GetSelection");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP SetSelection(TfEditCookie ec, ULONG ulCount,
+                         const TF_SELECTION *pSelection)
+  {
+    NS_NOTREACHED("ITfContext::SetSelection");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP GetStart(TfEditCookie ec, ITfRange **ppStart)
+  {
+    NS_NOTREACHED("ITfContext::GetStart");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP GetEnd(TfEditCookie ec, ITfRange **ppEnd)
+  {
+    NS_NOTREACHED("ITfContext::GetEnd");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP GetActiveView(ITfContextView **ppView)
+  {
+    NS_NOTREACHED("ITfContext::GetActiveView");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP EnumViews(IEnumTfContextViews **ppEnum)
+  {
+    NS_NOTREACHED("ITfContext::EnumViews");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP GetStatus(TF_STATUS *pdcs)
+  {
+    NS_NOTREACHED("ITfContext::GetStatus");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP GetProperty(REFGUID guidProp, ITfProperty **ppProp)
+  {
+    NS_NOTREACHED("ITfContext::GetProperty");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP GetAppProperty(REFGUID guidProp, ITfReadOnlyProperty **ppProp)
+  {
+    NS_NOTREACHED("ITfContext::GetAppProperty");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP TrackProperties(const GUID **prgProp, ULONG cProp,
+                            const GUID **prgAppProp, ULONG cAppProp,
+                            ITfReadOnlyProperty **ppProperty)
+  {
+    NS_NOTREACHED("ITfContext::TrackProperties");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP EnumProperties(IEnumTfProperties **ppEnum)
+  {
+    NS_NOTREACHED("ITfContext::EnumProperties");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP GetDocumentMgr(ITfDocumentMgr **ppDm)
+  {
+    NS_NOTREACHED("ITfContext::GetDocumentMgr");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP CreateRangeBackup(TfEditCookie ec, ITfRange *pRange,
+                              ITfRangeBackup **ppBackup)
+  {
+    NS_NOTREACHED("ITfContext::CreateRangeBackup");
+    return E_NOTIMPL;
+  }
+
+public: // ITfRangeACP
+
+  STDMETHODIMP GetText(TfEditCookie ec, DWORD dwFlags, WCHAR *pchText,
+                    ULONG cchMax, ULONG *pcch)
+  {
+    NS_NOTREACHED("ITfRangeACP::GetText");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP SetText(TfEditCookie ec, DWORD dwFlags, const WCHAR *pchText,
+                    LONG cch)
+  {
+    NS_NOTREACHED("ITfRangeACP::SetText");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP GetFormattedText(TfEditCookie ec, IDataObject **ppDataObject)
+  {
+    NS_NOTREACHED("ITfRangeACP::GetFormattedText");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP GetEmbedded(TfEditCookie ec, REFGUID rguidService, REFIID riid,
+                        IUnknown **ppunk)
+  {
+    NS_NOTREACHED("ITfRangeACP::GetEmbedded");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP InsertEmbedded(TfEditCookie ec, DWORD dwFlags,
+                           IDataObject *pDataObject)
+  {
+    NS_NOTREACHED("ITfRangeACP::InsertEmbedded");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP ShiftStart(TfEditCookie ec, LONG cchReq, LONG *pcch,
+                       const TF_HALTCOND *pHalt)
+  {
+    NS_NOTREACHED("ITfRangeACP::ShiftStart");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP ShiftEnd(TfEditCookie ec, LONG cchReq, LONG *pcch,
+                     const TF_HALTCOND *pHalt)
+  {
+    NS_NOTREACHED("ITfRangeACP::ShiftEnd");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP ShiftStartToRange(TfEditCookie ec, ITfRange *pRange,
+                                 TfAnchor aPos)
+  {
+    NS_NOTREACHED("ITfRangeACP::ShiftStartToRange");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP ShiftEndToRange(TfEditCookie ec, ITfRange *pRange,
+                               TfAnchor aPos)
+  {
+    NS_NOTREACHED("ITfRangeACP::ShiftEndToRange");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP ShiftStartRegion(TfEditCookie ec, TfShiftDir dir,
+                                BOOL *pfNoRegion)
+  {
+    NS_NOTREACHED("ITfRangeACP::ShiftStartRegion");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP ShiftEndRegion(TfEditCookie ec, TfShiftDir dir,
+                              BOOL *pfNoRegion)
+  {
+    NS_NOTREACHED("ITfRangeACP::ShiftEndRegion");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP IsEmpty(TfEditCookie ec, BOOL *pfEmpty)
+  {
+    NS_NOTREACHED("ITfRangeACP::IsEmpty");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP Collapse(TfEditCookie ec, TfAnchor aPos)
+  {
+    NS_NOTREACHED("ITfRangeACP::Collapse");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP IsEqualStart(TfEditCookie ec, ITfRange *pWith,
+                            TfAnchor aPos, BOOL *pfEqual)
+  {
+    NS_NOTREACHED("ITfRangeACP::IsEqualStart");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP IsEqualEnd(TfEditCookie ec, ITfRange *pWith,
+                          TfAnchor aPos, BOOL *pfEqual)
+  {
+    NS_NOTREACHED("ITfRangeACP::IsEqualEnd");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP CompareStart(TfEditCookie ec, ITfRange *pWith,
+                            TfAnchor aPos, LONG *plResult)
+  {
+    NS_NOTREACHED("ITfRangeACP::CompareStart");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP CompareEnd(TfEditCookie ec, ITfRange *pWith,
+                          TfAnchor aPos, LONG *plResult)
+  {
+    NS_NOTREACHED("ITfRangeACP::CompareEnd");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP AdjustForInsert(TfEditCookie ec, ULONG cchInsert,
+                               BOOL *pfInsertOk)
+  {
+    NS_NOTREACHED("ITfRangeACP::AdjustForInsert");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP GetGravity(TfGravity *pgStart, TfGravity *pgEnd)
+  {
+    NS_NOTREACHED("ITfRangeACP::GetGravity");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP SetGravity(TfEditCookie ec, TfGravity gStart, TfGravity gEnd)
+  {
+    NS_NOTREACHED("ITfRangeACP::SetGravity");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP Clone(ITfRange **ppClone)
+  {
+    NS_NOTREACHED("ITfRangeACP::Clone");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP GetContext(ITfContext **ppContext)
+  {
+    NS_NOTREACHED("ITfRangeACP::GetContext");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP GetExtent(LONG *pacpAnchor, LONG *pcch)
+  {
+    *pacpAnchor = LONG(mRangeStart);
+    *pcch = LONG(mRangeLength);
+    return S_OK;
+  }
+
+  STDMETHODIMP SetExtent(LONG acpAnchor, LONG cch)
+  {
+    mRangeStart = PRUint32(acpAnchor);
+    mRangeLength = PRUint32(cch);
+    return S_OK;
+  }
+
+public: // ITfCompositionView
+
+  STDMETHODIMP GetOwnerClsid(CLSID* pclsid)
+  {
+    NS_NOTREACHED("ITfCompositionView::GetOwnerClsid");
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP GetRange(ITfRange** ppRange)
+  {
+    (*ppRange) = this;
+    (*ppRange)->AddRef();
+    return S_OK;
+  }
+
+public: // ITextStoreACPSink
+
+  STDMETHODIMP OnTextChange(DWORD dwFlags, const TS_TEXTCHANGE *pChange)
+  {
+    mTextChanged = PR_TRUE;
+    mTextChangeData = *pChange;
+    return S_OK;
+  }
+
+  STDMETHODIMP OnSelectionChange(void)
+  {
+    mSelChanged = PR_TRUE;
+    return S_OK;
+  }
+
+  STDMETHODIMP OnLayoutChange(TsLayoutCode lcode, TsViewCookie vcView)
+  {
+    return S_OK;
+  }
+
+  STDMETHODIMP OnStatusChange(DWORD dwFlags)
+  {
+    return S_OK;
+  }
+
+  STDMETHODIMP OnAttrsChange(LONG acpStart, LONG acpEnd, ULONG cAttrs,
+                          const TS_ATTRID *paAttrs)
+  {
+    return S_OK;
+  }
+
+  STDMETHODIMP OnLockGranted(DWORD dwLockFlags)
+  {
+    // If we have a test, run it
+    if (mTest && !(mTestApp->*mTest)())
+      return S_FALSE;
+    return S_OK;
+  }
+
+  STDMETHODIMP OnStartEditTransaction(void)
+  {
+    return S_OK;
+  }
+
+  STDMETHODIMP OnEndEditTransaction(void)
+  {
+    return S_OK;
+  }
+};
+
+NS_IMPL_ISUPPORTS2(TestApp, nsIWebProgressListener,
+                            nsISupportsWeakReference)
+
+nsresult
+TestApp::Run(void)
+{
+  // Create a test window
+  // We need a full-fledged window to test for TSF functionality
+  nsresult rv;
+  mAppShell = do_GetService(kAppShellCID);
+  NS_ENSURE_TRUE(mAppShell, NS_ERROR_UNEXPECTED);
+
+  nsCOMPtr<nsIAppShellService> appShellService(
+      do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+  NS_ENSURE_TRUE(appShellService, NS_ERROR_UNEXPECTED);
+
+  nsCOMPtr<nsIURI> uri;
+  rv = NS_NewURI(getter_AddRefs(uri), "about:blank", nsnull);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = appShellService->CreateTopLevelWindow(nsnull, uri,
+                           nsIWebBrowserChrome::CHROME_DEFAULT,
+                           800 /*nsIAppShellService::SIZE_TO_CONTENT*/,
+                           600 /*nsIAppShellService::SIZE_TO_CONTENT*/,
+                           mAppShell, getter_AddRefs(mWindow));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIDocShell> docShell;
+  rv = mWindow->GetDocShell(getter_AddRefs(docShell));
+  NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED);
+  nsCOMPtr<nsIWebProgress> progress(do_GetInterface(docShell));
+  NS_ENSURE_TRUE(progress, NS_ERROR_UNEXPECTED);
+  rv = progress->AddProgressListener(this,
+                                     nsIWebProgress::NOTIFY_STATE_WINDOW |
+                                         nsIWebProgress::NOTIFY_STATUS);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mAppShell->Run();
+  return NS_OK;
+}
+
+PRBool
+TestApp::CheckFailed(void)
+{
+  // All windows should be closed by now
+  if (mImpl && !mImpl->mDeactivated) {
+    fail("TSF not terminated properly");
+    mFailed = PR_TRUE;
+  }
+  mImpl = nsnull;
+  return mFailed;
+}
+
+nsresult
+TestApp::Init(void)
+{
+  // Replace TSF manager pointer
+  nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(mWindow));
+  NS_ENSURE_TRUE(baseWindow, NS_ERROR_UNEXPECTED);
+  nsCOMPtr<nsIWidget> widget;
+  nsresult rv = baseWindow->GetMainWidget(getter_AddRefs(widget));
+  NS_ENSURE_TRUE(widget, NS_ERROR_UNEXPECTED);
+
+  ITfThreadMgr **mgr = reinterpret_cast<ITfThreadMgr**>(
+      widget->GetNativeData(NS_NATIVE_TSF_POINTER));
+  if (!mgr) {
+    fail("nsIWidget::GetNativeData(NS_NATIVE_TSF_POINTER) not supported");
+    return NS_ERROR_FAILURE;
+  }
+  if (*mgr) {
+    (*mgr)->Deactivate();
+    (*mgr)->Release();
+    (*mgr) = NULL;
+  } else {
+    // This is only for information. The test does not need TSF to run.
+    printf("TSF not initialized properly (TSF is not enabled/installed?)\n");
+  }
+
+  mImpl = new TSFImpl(this);
+  if (!mImpl) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  (*mgr) = mImpl;
+  (*mgr)->AddRef();
+
+  // Apply the change
+  reinterpret_cast<ITfThreadMgr**>(
+      widget->GetNativeData(NS_NATIVE_TSF_POINTER));
+
+  // Create a couple of text boxes for testing
+  nsCOMPtr<nsIDOMWindowInternal> win(do_GetInterface(mWindow));
+  NS_ENSURE_TRUE(win, NS_ERROR_UNEXPECTED);
+  nsCOMPtr<nsIDOMDocument> document;
+  rv = win->GetDocument(getter_AddRefs(document));
+  NS_ENSURE_TRUE(document, NS_ERROR_UNEXPECTED);
+  nsCOMPtr<nsIDOMHTMLDocument> htmlDoc(do_QueryInterface(document));
+  NS_ENSURE_TRUE(htmlDoc, NS_ERROR_UNEXPECTED);
+  nsCOMPtr<nsIDOMHTMLElement> htmlBody;
+  rv = htmlDoc->GetBody(getter_AddRefs(htmlBody));
+  NS_ENSURE_TRUE(htmlBody, NS_ERROR_UNEXPECTED);
+
+  nsCOMPtr<nsIDOMElement> form;
+  rv = htmlDoc->CreateElementNS(
+                     NS_LITERAL_STRING("http://www.w3.org/1999/xhtml"),
+                     NS_LITERAL_STRING("form"),
+                     getter_AddRefs(form));
+  nsCOMPtr<nsIDOMElement> elem;
+  rv = htmlDoc->CreateElementNS(
+                     NS_LITERAL_STRING("http://www.w3.org/1999/xhtml"),
+                     NS_LITERAL_STRING("input"),
+                     getter_AddRefs(elem));
+  NS_ENSURE_SUCCESS(rv, rv);
+  elem->SetAttribute(NS_LITERAL_STRING("type"),
+                      NS_LITERAL_STRING("text"));
+  mInput = do_QueryInterface(elem);
+  NS_ENSURE_TRUE(mInput, NS_ERROR_UNEXPECTED);
+  rv = htmlDoc->CreateElementNS(
+                     NS_LITERAL_STRING("http://www.w3.org/1999/xhtml"),
+                     NS_LITERAL_STRING("textarea"),
+                     getter_AddRefs(elem));
+  NS_ENSURE_SUCCESS(rv, rv);
+  mTextArea = do_QueryInterface(elem);
+  NS_ENSURE_TRUE(mTextArea, NS_ERROR_UNEXPECTED);
+  rv = htmlDoc->CreateElementNS(
+                     NS_LITERAL_STRING("http://www.w3.org/1999/xhtml"),
+                     NS_LITERAL_STRING("input"),
+                     getter_AddRefs(elem));
+  NS_ENSURE_SUCCESS(rv, rv);
+  elem->SetAttribute(NS_LITERAL_STRING("type"),
+                     NS_LITERAL_STRING("button"));
+  mButton = do_QueryInterface(elem);
+  NS_ENSURE_TRUE(mButton, NS_ERROR_UNEXPECTED);
+
+  nsCOMPtr<nsIDOMNode> node;
+  rv = form->AppendChild(mInput, getter_AddRefs(node));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = form->AppendChild(mTextArea, getter_AddRefs(node));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = form->AppendChild(mButton, getter_AddRefs(node));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = htmlBody->AppendChild(form, getter_AddRefs(node));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // set a background color manually,
+  // otherwise the window might be transparent
+  nsCOMPtr<nsIDOMHTMLBodyElement>(do_QueryInterface(htmlBody))->
+      SetBgColor(NS_LITERAL_STRING("white"));
+
+  widget->Show(PR_TRUE);
+  widget->SetFocus();
+  return NS_OK;
+}
+
+nsresult
+TestApp::Term(void)
+{
+  mCurrentNode = nsnull;
+  mInput = nsnull;
+  mTextArea = nsnull;
+  mButton = nsnull;
+
+  nsCOMPtr<nsIDOMWindowInternal> win(do_GetInterface(mWindow));
+  if (win)
+    win->Close();
+  win = nsnull;
+  mWindow = nsnull;
+
+  if (mAppShell)
+    mAppShell->Exit();
+  mAppShell = nsnull;
+  return NS_OK;
+}
+
+PRBool
+TestApp::RunTest(test_type aTest, PRBool aLock)
+{
+  PRBool succeeded;
+  if (aLock && mImpl->mStore) {
+    mImpl->mTest = aTest;
+    HRESULT hr = E_FAIL;
+    mImpl->mStore->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &hr);
+    succeeded = hr == S_OK;
+  } else {
+    succeeded = (this->*aTest)();
+  }
+  mFailed |= !succeeded;
+  return succeeded;
+}
+
+NS_IMETHODIMP
+TestApp::OnStateChange(nsIWebProgress *aWebProgress,
+                        nsIRequest *aRequest,
+                        PRUint32 aStateFlags,
+                        nsresult aStatus)
+{
+  NS_ASSERTION(aStateFlags & nsIWebProgressListener::STATE_IS_WINDOW &&
+              aStateFlags & nsIWebProgressListener::STATE_STOP, "wrong state");
+  if (NS_SUCCEEDED(Init())) {
+    if (RunTest(&TestApp::TestFocus, PR_FALSE))
+      passed("TestFocus");
+
+    mCurrentNode = mInput;
+    mInput->Focus();
+    if (mImpl->mStore) {
+      if (RunTest(&TestApp::TestClustering))
+        passed("TestClustering");
+    } else {
+      fail("no text store (clustering)");
+      mFailed = PR_TRUE;
+    }
+
+    printf("Testing TSF support in text input element...\n");
+    mCurrentNode = mInput;
+    mTestString = NS_LITERAL_STRING(
+      "This is a test of the Text Services Framework implementation.");
+    mInput->SetValue(mTestString);
+    mInput->Focus();
+    if (mImpl->mStore) {
+      if (RunTest(&TestApp::TestSelection))
+        passed("TestSelection (input)");
+      if (RunTest(&TestApp::TestText))
+        passed("TestText (input)");
+      if (RunTest(&TestApp::TestExtents))
+        passed("TestExtents (input)");
+      if (RunTest(&TestApp::TestComposition))
+        passed("TestComposition (input)");
+      if (RunTest(&TestApp::TestNotification, PR_FALSE))
+        passed("TestNotification (input)");
+    } else {
+      fail("no text store (input)");
+      mFailed = PR_TRUE;
+    }
+
+    printf("Testing TSF support in textarea element...\n");
+    mCurrentNode = mTextArea;
+    mTestString = NS_LITERAL_STRING(
+      "This is a test of the\r\nText Services Framework\r\nimplementation.");
+    mTextArea->SetValue(mTestString);
+    mTextArea->Focus();
+    if (mImpl->mStore) {
+      if (RunTest(&TestApp::TestSelection))
+        passed("TestSelection (textarea)");
+      if (RunTest(&TestApp::TestText))
+        passed("TestText (textarea)");
+      if (RunTest(&TestApp::TestExtents))
+        passed("TestExtents (textarea)");
+      if (RunTest(&TestApp::TestComposition))
+        passed("TestComposition (textarea)");
+      if (RunTest(&TestApp::TestNotification, PR_FALSE))
+        passed("TestNotification (textarea)");
+    } else {
+      fail("no text store (textarea)");
+      mFailed = PR_TRUE;
+    }
+  } else {
+    fail("initialization");
+    mFailed = PR_TRUE;
+  }
+  Term();
+  return NS_OK;
+}
+
+PRBool
+TestApp::TestFocus(void)
+{
+  PRUint32 focus = mImpl->mFocusCount, blur = mImpl->mBlurCount;
+  nsresult rv;
+
+  /* If these fail the cause is probably one or more of:
+   * - nsIMEStateManager::OnTextStateFocus not called by nsEventStateManager
+   * - nsIMEStateManager::OnTextStateBlur not called by nsEventStateManager
+   * - nsWindow::OnIMEFocusChange (nsIWidget) not called by nsIMEStateManager
+   * - nsTextStore::Create/Focus/Destroy not called by nsWindow
+   * - ITfThreadMgr::CreateDocumentMgr/SetFocus not called by nsTextStore
+   * - ITfDocumentMgr::CreateContext/Push not called by nsTextStore
+   */
+
+  rv = mInput->Focus();
+  if (!(NS_SUCCEEDED(rv) &&
+        mImpl->mFocused &&
+        mImpl->mStore &&
+        mImpl->mFocusCount - focus == 1 &&
+        mImpl->mBlurCount - blur == 0 &&
+        mImpl->mStore)) {
+    fail("TestFocus: document focus was not set");
+    return PR_FALSE;
+  }
+
+  rv = mTextArea->Focus();
+  if (!(NS_SUCCEEDED(rv) &&
+        mImpl->mFocused &&
+        mImpl->mStore &&
+        mImpl->mFocusCount - focus == 2 &&
+        mImpl->mBlurCount - blur == 1 &&
+        mImpl->mStore)) {
+    fail("TestFocus: document focus was not changed");
+    return PR_FALSE;
+  }
+
+  rv = mButton->Focus();
+  if (!(NS_SUCCEEDED(rv) &&
+        !mImpl->mFocused &&
+        !mImpl->mStore &&
+        mImpl->mFocusCount - focus == 2 &&
+        mImpl->mBlurCount - blur == 2 &&
+        !mImpl->mStore)) {
+    fail("TestFocus: document was not blurred");
+    return PR_FALSE;
+  }
+  return PR_TRUE;
+}
+
+PRBool
+TestApp::TestClustering(void)
+{
+  // Text for testing
+  const PRUint32 STRING_LENGTH = 2;
+  PRUnichar string[3];
+  string[0] = 'e';
+  string[1] = 0x0301; // U+0301 'acute accent'
+  string[2] = nsnull;
+
+  // Replace entire string with our string
+  TS_TEXTCHANGE textChange;
+  HRESULT hr = mImpl->mStore->SetText(0, 0, -1, string, STRING_LENGTH,
+                                      &textChange);
+  if (!(SUCCEEDED(hr) &&
+        0 == textChange.acpStart &&
+        STRING_LENGTH == textChange.acpNewEnd)) {
+    fail("TestClustering: SetText");
+    return PR_FALSE;
+  }
+
+  TsViewCookie view;
+  RECT rectLetter, rectAccent, rectWhole, rectCombined;
+  BOOL clipped, nonEmpty;
+
+  hr = mImpl->mStore->GetActiveView(&view);
+  if (!(SUCCEEDED(hr))) {
+    fail("TestClustering: GetActiveView");
+    return PR_FALSE;
+  }
+
+  // Get rect of first char (the letter)
+  hr = mImpl->mStore->GetTextExt(view, 0, STRING_LENGTH / 2,
+                                 &rectLetter, &clipped);
+  if (!(SUCCEEDED(hr))) {
+    fail("TestClustering: GetTextExt (letter)");
+    return PR_FALSE;
+  }
+
+  // Get rect of second char (the accent)
+  hr = mImpl->mStore->GetTextExt(view, STRING_LENGTH / 2, STRING_LENGTH,
+                                 &rectAccent, &clipped);
+  if (!(SUCCEEDED(hr))) {
+    fail("TestClustering: GetTextExt (accent)");
+    return PR_FALSE;
+  }
+
+  // Get rect of combined char
+  hr = mImpl->mStore->GetTextExt(view, 0, STRING_LENGTH,
+                                 &rectWhole, &clipped);
+  if (!(SUCCEEDED(hr))) {
+    fail("TestClustering: GetTextExt (whole)");
+    return PR_FALSE;
+  }
+
+  nonEmpty = ::UnionRect(&rectCombined, &rectLetter, &rectAccent);
+  if (!(nonEmpty &&
+        ::EqualRect(&rectCombined, &rectWhole))) {
+    fail("TestClustering: unexpected combined rect");
+    return PR_FALSE;
+  }
+  return PR_TRUE;
+}
+
+PRBool
+TestApp::TestSelectionInternal(char* aTestName,
+                               LONG aStart,
+                               LONG aEnd,
+                               TsActiveSelEnd aSelEnd)
+{
+  PRBool succeeded = PR_TRUE, continueTest = PR_TRUE;
+  TS_SELECTION_ACP sel, testSel;
+  ULONG selFetched;
+
+  sel.acpStart = aStart;
+  sel.acpEnd = aEnd;
+  sel.style.ase = aSelEnd;
+  sel.style.fInterimChar = FALSE;
+  HRESULT hr = mImpl->mStore->SetSelection(1, &sel);
+  if (!(SUCCEEDED(hr))) {
+    fail("TestSelection: SetSelection (%s)", aTestName);
+    continueTest = succeeded = PR_FALSE;
+  }
+
+  if (continueTest) {
+    hr = mImpl->mStore->GetSelection(TS_DEFAULT_SELECTION, 1,
+                                     &testSel, &selFetched);
+    if (!(SUCCEEDED(hr) &&
+          selFetched == 1 &&
+          !memcmp(&sel, &testSel, sizeof(sel)))) {
+      fail("TestSelection: unexpected GetSelection result (%s)", aTestName);
+      succeeded = PR_FALSE;
+    }
+  }
+  return succeeded;
+}
+
+PRBool
+TestApp::TestSelection(void)
+{
+  PRBool succeeded = PR_TRUE;
+
+  /* If these fail the cause is probably one or more of:
+   * nsTextStore::GetSelection not sending NS_QUERY_SELECTED_TEXT
+   * NS_QUERY_SELECTED_TEXT not handled by nsContentEventHandler
+   * Bug in NS_QUERY_SELECTED_TEXT handler
+   * nsTextStore::SetSelection not sending NS_SELECTION_SET
+   * NS_SELECTION_SET not handled by nsContentEventHandler
+   * Bug in NS_SELECTION_SET handler
+   */
+
+  TS_SELECTION_ACP testSel;
+  ULONG selFetched;
+
+  HRESULT hr = mImpl->mStore->GetSelection(0, 1, &testSel, &selFetched);
+  if (!(SUCCEEDED(hr) &&
+        selFetched == 1)) {
+    fail("TestSelection: GetSelection");
+    succeeded = PR_FALSE;
+  }
+
+  const LONG SELECTION1_START            = 0;
+  const LONG SELECTION1_END              = mTestString.Length();
+  const TsActiveSelEnd SELECTION1_SELEND = TS_AE_END;
+
+  if (!TestSelectionInternal("normal",
+                             SELECTION1_START,
+                             SELECTION1_END,
+                             SELECTION1_SELEND)) {
+    succeeded = PR_FALSE;
+  }
+
+  const LONG SELECTION2_START            = mTestString.Length() / 2;
+  const LONG SELECTION2_END              = SELECTION2_START;
+  const TsActiveSelEnd SELECTION2_SELEND = TS_AE_END;
+
+  if (!TestSelectionInternal("collapsed",
+                             SELECTION2_START,
+                             SELECTION2_END,
+                             SELECTION2_SELEND)) {
+    succeeded = PR_FALSE;
+  }
+
+  const LONG SELECTION3_START            = 12;
+  const LONG SELECTION3_END              = mTestString.Length() - 20;
+  const TsActiveSelEnd SELECTION3_SELEND = TS_AE_START;
+
+  if (!TestSelectionInternal("reversed",
+                             SELECTION3_START,
+                             SELECTION3_END,
+                             SELECTION3_SELEND)) {
+    succeeded = PR_FALSE;
+  }
+  return succeeded;
+}
+
+PRBool
+TestApp::TestText(void)
+{
+  const PRUint32 BUFFER_SIZE  = (0x100);
+  const PRUint32 RUNINFO_SIZE = (0x10);
+
+  PRBool succeeded = PR_TRUE, continueTest;
+  PRUnichar buffer[BUFFER_SIZE];
+  TS_RUNINFO runInfo[RUNINFO_SIZE];
+  ULONG bufferRet, runInfoRet;
+  LONG acpRet, acpCurrent;
+  TS_TEXTCHANGE textChange;
+  HRESULT hr;
+
+  /* If these fail the cause is probably one or more of:
+   * nsTextStore::GetText not sending NS_QUERY_TEXT_CONTENT
+   * NS_QUERY_TEXT_CONTENT not handled by nsContentEventHandler
+   * Bug in NS_QUERY_TEXT_CONTENT handler
+   * nsTextStore::SetText not calling SetSelection or InsertTextAtSelection
+   * Bug in SetSelection or InsertTextAtSelection
+   *  NS_SELECTION_SET bug or NS_COMPOSITION_* / NS_TEXT_TEXT bug
+   */
+
+  // Get all text
+  hr = mImpl->mStore->GetText(0, -1, buffer, BUFFER_SIZE, &bufferRet,
+      runInfo, RUNINFO_SIZE, &runInfoRet, &acpRet);
+  if (!(SUCCEEDED(hr) &&
+        bufferRet <= mTestString.Length() &&
+        !wcsncmp(mTestString.get(), buffer, bufferRet) &&
+        acpRet == LONG(bufferRet) &&
+        runInfoRet > 0)) {
+    fail("TestText: GetText 1");
+    succeeded = PR_FALSE;
+  }
+
+
+  // Get text from GETTEXT2_START to GETTEXT2_END
+  const PRUint32 GETTEXT2_START       = (18);
+  const PRUint32 GETTEXT2_END         = (mTestString.Length() - 16);
+  const PRUint32 GETTEXT2_BUFFER_SIZE = (0x10);
+
+  hr = mImpl->mStore->GetText(GETTEXT2_START, GETTEXT2_END,
+      buffer, GETTEXT2_BUFFER_SIZE, &bufferRet,
+      runInfo, RUNINFO_SIZE, &runInfoRet, &acpRet);
+  if (!(SUCCEEDED(hr) &&
+        bufferRet <= GETTEXT2_BUFFER_SIZE &&
+        !wcsncmp(mTestString.get() + GETTEXT2_START, buffer, bufferRet) &&
+        acpRet == LONG(bufferRet) + GETTEXT2_START &&
+        runInfoRet > 0)) {
+    fail("TestText: GetText 2");
+    succeeded = PR_FALSE;
+  }
+
+
+  // Replace text from SETTEXT1_START to SETTEXT1_END with insertString
+  const PRUint32 SETTEXT1_START        = (8);
+  const PRUint32 SETTEXT1_TAIL_LENGTH  = (40);
+  const PRUint32 SETTEXT1_END          = (mTestString.Length() -
+                                          SETTEXT1_TAIL_LENGTH);
+  NS_NAMED_LITERAL_STRING(insertString, "(Inserted string)");
+
+  continueTest = PR_TRUE;
+  hr = mImpl->mStore->SetText(0, SETTEXT1_START, SETTEXT1_END,
+      insertString.get(), insertString.Length(), &textChange);
+  if (!(SUCCEEDED(hr) &&
+        textChange.acpStart == SETTEXT1_START &&
+        textChange.acpOldEnd == LONG(SETTEXT1_END) &&
+        textChange.acpNewEnd == LONG(SETTEXT1_START +
+                                insertString.Length()))) {
+    fail("TestText: SetText 1");
+    continueTest = succeeded = PR_FALSE;
+  }
+
+  const PRUint32 SETTEXT1_FINAL_LENGTH = (SETTEXT1_START +
+                                          SETTEXT1_TAIL_LENGTH +
+                                          insertString.Length());
+
+  if (continueTest) {
+    acpCurrent = 0;
+    while (acpCurrent < LONG(SETTEXT1_FINAL_LENGTH)) {
+      hr = mImpl->mStore->GetText(acpCurrent, -1, &buffer[acpCurrent],
+                                  BUFFER_SIZE, &bufferRet, runInfo,
+                                  RUNINFO_SIZE, &runInfoRet, &acpRet);
+      if (!(SUCCEEDED(hr) &&
+            acpRet > acpCurrent &&
+            bufferRet <= SETTEXT1_FINAL_LENGTH &&
+            runInfoRet > 0)) {
+        fail("TestText: GetText failed after SetTest 1");
+        continueTest = succeeded = PR_FALSE;
+        break;
+      }
+      acpCurrent = acpRet;
+    }
+  }
+
+  if (continueTest) {
+    if (!(acpCurrent == LONG(SETTEXT1_FINAL_LENGTH) &&
+          !wcsncmp(buffer, mTestString.get(), SETTEXT1_START) &&
+          !wcsncmp(&buffer[SETTEXT1_START], insertString.get(),
+                   insertString.Length()) &&
+          !wcsncmp(&buffer[SETTEXT1_START + insertString.Length()],
+                   mTestString.get() + SETTEXT1_END, SETTEXT1_TAIL_LENGTH))) {
+      fail("TestText: unexpected GetText result after SetText 1");
+      succeeded = PR_FALSE;
+    }
+  }
+
+
+  // Restore entire text to original text (mTestString)
+  continueTest = PR_TRUE;
+  hr = mImpl->mStore->SetText(0, 0, -1, mTestString.get(),
+      mTestString.Length(), &textChange);
+  if (!(SUCCEEDED(hr) &&
+        textChange.acpStart == 0 &&
+        textChange.acpOldEnd == LONG(SETTEXT1_FINAL_LENGTH) &&
+        textChange.acpNewEnd == LONG(mTestString.Length()))) {
+    fail("TestText: SetText 2");
+    continueTest = succeeded = PR_FALSE;
+  }
+
+  if (continueTest) {
+    acpCurrent = 0;
+    while (acpCurrent < LONG(mTestString.Length())) {
+      hr = mImpl->mStore->GetText(acpCurrent, -1, &buffer[acpCurrent],
+          BUFFER_SIZE, &bufferRet, runInfo, RUNINFO_SIZE, &runInfoRet, &acpRet);
+      if (!(SUCCEEDED(hr) &&
+            acpRet > acpCurrent &&
+            bufferRet <= mTestString.Length() &&
+            runInfoRet > 0)) {
+        fail("TestText: GetText failed after SetText 2");
+        continueTest = succeeded = PR_FALSE;
+        break;
+      }
+      acpCurrent = acpRet;
+    }
+  }
+
+  if (continueTest) {
+    if (!(acpCurrent == LONG(mTestString.Length()) &&
+          !wcsncmp(buffer, mTestString.get(), mTestString.Length()))) {
+      fail("TestText: unexpected GetText result after SetText 2");
+      succeeded = PR_FALSE;
+    }
+  }
+  return succeeded;
+}
+
+PRBool
+TestApp::TestExtents(void)
+{
+  TS_SELECTION_ACP sel;
+  sel.acpStart = 0;
+  sel.acpEnd = 0;
+  sel.style.ase = TS_AE_END;
+  sel.style.fInterimChar = FALSE;
+  mImpl->mStore->SetSelection(1, &sel);
+
+  nsCOMPtr<nsISelectionController> selCon;
+  if (!(NS_SUCCEEDED(GetSelCon(getter_AddRefs(selCon))) && selCon)) {
+    fail("TestExtents: get nsISelectionController");
+    return PR_FALSE;
+  }
+  selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
+              nsISelectionController::SELECTION_FOCUS_REGION, PR_TRUE);
+
+  nsCOMPtr<nsIDOMWindowInternal> window(do_GetInterface(mWindow));
+  if (!window) {
+    fail("TestExtents: get nsIDOMWindowInternal");
+    return PR_FALSE;
+  }
+  RECT windowRect, screenRect, textRect1, textRect2;
+  BOOL clipped;
+  PRInt32 val;
+  TsViewCookie view;
+  HRESULT hr;
+
+  nsresult nsr = window->GetScreenX(&val);
+  windowRect.left = val;
+  nsr |= window->GetScreenY(&val);
+  windowRect.top = val;
+  nsr |= window->GetOuterWidth(&val);
+  windowRect.right = windowRect.left + val;
+  nsr |= window->GetOuterHeight(&val);
+  windowRect.bottom = windowRect.top + val;
+  if (!(NS_SUCCEEDED(nsr))) {
+    fail("TestExtents: get window rect failed");
+    return PR_FALSE;
+  }
+
+  hr = mImpl->mStore->GetActiveView(&view);
+  if (!(SUCCEEDED(hr))) {
+    fail("TestExtents: GetActiveView");
+    return PR_FALSE;
+  }
+
+  PRBool succeeded = PR_TRUE;
+  HWND hwnd;
+  hr = mImpl->mStore->GetWnd(view, &hwnd);
+  if (!(SUCCEEDED(hr) &&
+        ::IsWindow(hwnd))) {
+    fail("TestExtents: GetWnd");
+    succeeded = PR_FALSE;
+  }
+
+  ::SetRectEmpty(&screenRect);
+  hr = mImpl->mStore->GetScreenExt(view, &screenRect);
+  if (!(SUCCEEDED(hr) &&
+        screenRect.left > windowRect.left &&
+        screenRect.top > windowRect.top &&
+        screenRect.right > screenRect.left &&
+        screenRect.bottom > screenRect.top &&
+        screenRect.right < windowRect.right &&
+        screenRect.bottom < windowRect.bottom)) {
+    fail("TestExtents: GetScreenExt");
+    succeeded = PR_FALSE;
+  }
+
+  const LONG GETTEXTEXT1_START = 0;
+  const LONG GETTEXTEXT1_END   = 0;
+
+  ::SetRectEmpty(&textRect1);
+  hr = mImpl->mStore->GetTextExt(view, GETTEXTEXT1_START, GETTEXTEXT1_END,
+      &textRect1, &clipped);
+  if (!(SUCCEEDED(hr) &&
+        textRect1.left >= screenRect.left &&
+        textRect1.top >= screenRect.top &&
+        textRect1.right < screenRect.right &&
+        textRect1.bottom <= screenRect.bottom &&
+        textRect1.right >= textRect1.left &&
+        textRect1.bottom > textRect1.top)) {
+    fail("TestExtents: GetTextExt (offset %ld to %ld)",
+         GETTEXTEXT1_START, GETTEXTEXT1_END);
+    succeeded = PR_FALSE;
+  }
+
+  const LONG GETTEXTEXT2_START = 10;
+  const LONG GETTEXTEXT2_END   = 25;
+
+  ::SetRectEmpty(&textRect2);
+  hr = mImpl->mStore->GetTextExt(view, GETTEXTEXT2_START, GETTEXTEXT2_END,
+      &textRect2, &clipped);
+  if (!(SUCCEEDED(hr) &&
+        textRect2.left >= screenRect.left &&
+        textRect2.top >= screenRect.top &&
+        textRect2.right <= screenRect.right &&
+        textRect2.bottom <= screenRect.bottom &&
+        textRect2.right > textRect2.left &&
+        textRect2.bottom > textRect2.top)) {
+    fail("TestExtents: GetTextExt (offset %ld to %ld)",
+         GETTEXTEXT2_START, GETTEXTEXT2_END);
+    succeeded = PR_FALSE;
+  }
+
+  // Offsets must be between GETTEXTEXT2_START and GETTEXTEXT2_END
+  const LONG GETTEXTEXT3_START = 23;
+  const LONG GETTEXTEXT3_END   = 23;
+
+  ::SetRectEmpty(&textRect1);
+  hr = mImpl->mStore->GetTextExt(view, GETTEXTEXT3_START, GETTEXTEXT3_END,
+        &textRect1, &clipped);
+  // Rectangle must be entirely inside the previous rectangle,
+  // since GETTEXTEXT3_START and GETTEXTEXT3_END are between
+  // GETTEXTEXT2_START and GETTEXTEXT2_START
+  if (!(SUCCEEDED(hr) && ::IsRectEmpty(&textRect1) ||
+        (textRect1.left >= textRect2.left &&
+        textRect1.top >= textRect2.top &&
+        textRect1.right <= textRect2.right &&
+        textRect1.bottom <= textRect2.bottom &&
+        textRect1.right >= textRect1.left &&
+        textRect1.bottom > textRect1.top))) {
+    fail("TestExtents: GetTextExt (offset %ld to %ld)",
+         GETTEXTEXT3_START, GETTEXTEXT3_END);
+    succeeded = PR_FALSE;
+  }
+  return succeeded;
+}
+
+PRBool
+TestApp::TestCompositionSelectionAndText(char* aTestName,
+                                         LONG aExpectedSelStart,
+                                         LONG aExpectedSelEnd,
+                                         nsString& aReferenceString)
+{
+  TS_SELECTION_ACP currentSel;
+  ULONG selFetched = 0;
+  HRESULT hr = mImpl->mStore->GetSelection(TF_DEFAULT_SELECTION, 1,
+                                           &currentSel, &selFetched);
+  if (!(SUCCEEDED(hr) &&
+        1 == selFetched &&
+        currentSel.acpStart == aExpectedSelStart &&
+        currentSel.acpEnd == aExpectedSelEnd)) {
+    fail("TestComposition: GetSelection (%s)", aTestName);
+    return PR_FALSE;
+  }
+
+  const PRUint32 bufferSize = 0x100, runInfoSize = 0x10;
+  PRUnichar buffer[bufferSize];
+  TS_RUNINFO runInfo[runInfoSize];
+  ULONG bufferRet, runInfoRet;
+  LONG acpRet, acpCurrent = 0;
+  while (acpCurrent < LONG(aReferenceString.Length())) {
+    hr = mImpl->mStore->GetText(acpCurrent, aReferenceString.Length(),
+          &buffer[acpCurrent], bufferSize, &bufferRet, runInfo, runInfoSize,
+          &runInfoRet, &acpRet);
+    if (!(SUCCEEDED(hr) &&
+          acpRet > acpCurrent &&
+          bufferRet <= aReferenceString.Length() &&
+          runInfoRet > 0)) {
+      fail("TestComposition: GetText (%s)", aTestName);
+      return PR_FALSE;
+    }
+    acpCurrent = acpRet;
+  }
+  if (!(acpCurrent == aReferenceString.Length() &&
+        !wcsncmp(buffer, aReferenceString.get(), aReferenceString.Length()))) {
+    fail("TestComposition: unexpected GetText result (%s)", aTestName);
+    return PR_FALSE;
+  }
+  return PR_TRUE;
+}
+
+PRBool
+TestApp::TestComposition(void)
+{
+  nsRefPtr<ITfContextOwnerCompositionSink> sink;
+  HRESULT hr = mImpl->mStore->QueryInterface(
+                                  IID_ITfContextOwnerCompositionSink,
+                                  getter_AddRefs(sink));
+  if (!(SUCCEEDED(hr))) {
+    fail("TestComposition: QueryInterface");
+    return PR_FALSE;
+  }
+
+  const LONG PRECOMPOSITION_SEL_START            = 2;
+  const LONG PRECOMPOSITION_SEL_END              = PRECOMPOSITION_SEL_START;
+  const TsActiveSelEnd PRECOMPOSITION_SEL_SELEND = TS_AE_END;
+
+  TS_SELECTION_ACP sel;
+  sel.acpStart = PRECOMPOSITION_SEL_START;
+  sel.acpEnd = PRECOMPOSITION_SEL_END;
+  sel.style.ase = PRECOMPOSITION_SEL_SELEND;
+  sel.style.fInterimChar = FALSE;
+  hr = mImpl->mStore->SetSelection(1, &sel);
+  if (!(SUCCEEDED(hr))) {
+    fail("TestComposition: SetSelection (pre-composition)");
+    return PR_FALSE;
+  }
+
+
+  TS_TEXTCHANGE textChange;
+  NS_NAMED_LITERAL_STRING(insertString1, "Compo1");
+  hr = mImpl->mStore->InsertTextAtSelection(TF_IAS_NOQUERY,
+                                            insertString1.get(),
+                                            insertString1.Length(),
+                                            NULL, NULL, &textChange);
+  if (!(SUCCEEDED(hr) &&
+        sel.acpEnd == textChange.acpStart &&
+        sel.acpEnd == textChange.acpOldEnd &&
+        sel.acpEnd + insertString1.Length() == textChange.acpNewEnd)) {
+    fail("TestComposition: InsertTextAtSelection");
+    return PR_FALSE;
+  }
+  sel.acpEnd = textChange.acpNewEnd;
+
+  mImpl->mRangeStart = textChange.acpStart;
+  mImpl->mRangeLength = textChange.acpNewEnd - textChange.acpOldEnd;
+  BOOL okay = FALSE;
+  hr = sink->OnStartComposition(mImpl, &okay);
+  if (!(SUCCEEDED(hr) &&
+        okay)) {
+    fail("TestComposition: OnStartComposition");
+    return PR_FALSE;
+  }
+
+
+  NS_NAMED_LITERAL_STRING(insertString2, "Composition2");
+  hr = mImpl->mStore->SetText(0, mImpl->mRangeStart + mImpl->mRangeLength,
+                              mImpl->mRangeStart + mImpl->mRangeLength,
+                              insertString2.get(), insertString2.Length(),
+                              &textChange);
+  if (!(SUCCEEDED(hr) &&
+        sel.acpEnd == textChange.acpStart &&
+        sel.acpEnd == textChange.acpOldEnd &&
+        sel.acpEnd + insertString2.Length() == textChange.acpNewEnd)) {
+    fail("TestComposition: SetText 1");
+    return PR_FALSE;
+  }
+  sel.acpEnd = textChange.acpNewEnd;
+  mImpl->mRangeLength += textChange.acpNewEnd - textChange.acpOldEnd;
+
+
+  const LONG COMPOSITION3_TEXT_START_OFFSET = -8; // offset 8 from the end
+  const LONG COMPOSITION3_TEXT_END_OFFSET   = 4;
+
+  const LONG COMPOSITION3_TEXT_START = mImpl->mRangeStart +
+                                       mImpl->mRangeLength +
+                                       COMPOSITION3_TEXT_START_OFFSET;
+  const LONG COMPOSITION3_TEXT_END   = COMPOSITION3_TEXT_START +
+                                       COMPOSITION3_TEXT_END_OFFSET;
+
+  NS_NAMED_LITERAL_STRING(insertString3, "Compo3");
+  hr = mImpl->mStore->SetText(0, COMPOSITION3_TEXT_START,
+                              COMPOSITION3_TEXT_END,
+                              insertString3.get(), insertString3.Length(),
+                              &textChange);
+  if (!(SUCCEEDED(hr) &&
+        sel.acpEnd + COMPOSITION3_TEXT_START_OFFSET == textChange.acpStart &&
+        sel.acpEnd + COMPOSITION3_TEXT_START_OFFSET +
+            COMPOSITION3_TEXT_END_OFFSET == textChange.acpOldEnd &&
+        sel.acpEnd + insertString3.Length() + COMPOSITION3_TEXT_START_OFFSET ==
+            textChange.acpNewEnd)) {
+    fail("TestComposition: SetText 2");
+    return PR_FALSE;
+  }
+  sel.acpEnd = textChange.acpNewEnd;
+  mImpl->mRangeLength += textChange.acpNewEnd - textChange.acpOldEnd;
+
+
+  nsString referenceString;
+  referenceString.Append(mTestString.get(), sel.acpStart);
+  referenceString.Append(insertString1);
+  referenceString.Append(insertString2.get(),
+      insertString2.Length() + COMPOSITION3_TEXT_START_OFFSET);
+  referenceString.Append(insertString3);
+  referenceString.Append(insertString2.get() + insertString2.Length() -
+      COMPOSITION3_TEXT_END_OFFSET, COMPOSITION3_TEXT_END_OFFSET);
+  referenceString.Append(mTestString.get() + sel.acpStart,
+      COMPOSITION3_TEXT_END_OFFSET);
+
+  if (!TestCompositionSelectionAndText("composition",
+           sel.acpEnd, sel.acpEnd,
+           referenceString))
+    return PR_FALSE;
+
+
+  const LONG POSTCOMPOSITION_SEL_START = sel.acpEnd - 8;
+  const LONG POSTCOMPOSITION_SEL_END   = POSTCOMPOSITION_SEL_START + 2;
+
+  sel.acpStart = POSTCOMPOSITION_SEL_START;
+  sel.acpEnd = POSTCOMPOSITION_SEL_END;
+  hr = mImpl->mStore->SetSelection(1, &sel);
+  if (!(SUCCEEDED(hr))) {
+    fail("TestComposition: SetSelection (composition)");
+    return PR_FALSE;
+  }
+
+  hr = sink->OnEndComposition(mImpl);
+  if (!(SUCCEEDED(hr))) {
+    fail("TestComposition: OnEndComposition");
+    return PR_FALSE;
+  }
+
+  if (!TestCompositionSelectionAndText("post-composition",
+           sel.acpStart, sel.acpEnd,
+           referenceString))
+    return PR_FALSE;
+
+
+  const LONG EMPTYCOMPOSITION_START  = mImpl->mRangeStart + 2;
+  const LONG EMPTYCOMPOSITION_LENGTH = mImpl->mRangeLength - 4;
+
+  mImpl->mRangeStart = EMPTYCOMPOSITION_START;
+  mImpl->mRangeLength = EMPTYCOMPOSITION_LENGTH;
+  okay = FALSE;
+  hr = sink->OnStartComposition(mImpl, &okay);
+  if (!(SUCCEEDED(hr) &&
+        okay)) {
+    fail("TestComposition: OnStartComposition (empty composition)");
+    return PR_FALSE;
+  }
+
+  hr = sink->OnEndComposition(mImpl);
+  if (!(SUCCEEDED(hr))) {
+    fail("TestComposition: OnEndComposition (empty composition)");
+    return PR_FALSE;
+  }
+
+  if (!TestCompositionSelectionAndText("empty composition",
+           mImpl->mRangeStart,
+           mImpl->mRangeStart + mImpl->mRangeLength,
+           referenceString))
+    return PR_FALSE;
+
+  return PR_TRUE;
+}
+
+PRBool
+TestApp::TestNotificationTextChange(nsIWidget* aWidget,
+                                    PRUint32 aCode,
+                                    const nsAString& aCharacter,
+                                    LONG aStart,
+                                    LONG aOldEnd,
+                                    LONG aNewEnd)
+{
+  MSG msg;
+  if (::PeekMessageW(&msg, NULL, WM_USER_TSF_TEXTCHANGE,
+                     WM_USER_TSF_TEXTCHANGE, PM_REMOVE))
+    ::DispatchMessageW(&msg);
+  mImpl->mTextChanged = PR_FALSE;
+  nsresult nsr = aWidget->SynthesizeNativeKeyEvent(0, aCode, 0,
+                              aCharacter, aCharacter);
+  if (::PeekMessageW(&msg, NULL, WM_USER_TSF_TEXTCHANGE,
+                     WM_USER_TSF_TEXTCHANGE, PM_REMOVE))
+    ::DispatchMessageW(&msg);
+  return NS_SUCCEEDED(nsr) &&
+         mImpl->mTextChanged &&
+         aStart == mImpl->mTextChangeData.acpStart &&
+         aOldEnd == mImpl->mTextChangeData.acpOldEnd &&
+         aNewEnd == mImpl->mTextChangeData.acpNewEnd;
+}
+
+PRBool
+TestApp::TestNotification(void)
+{
+  nsresult nsr;
+  // get selection to test notification support
+  nsCOMPtr<nsISelectionController> selCon;
+  if (!(NS_SUCCEEDED(GetSelCon(getter_AddRefs(selCon))) && selCon)) {
+    fail("TestNotification: get nsISelectionController");
+    return PR_FALSE;
+  }
+
+  nsr = selCon->CompleteMove(PR_FALSE, PR_FALSE);
+  if (!(NS_SUCCEEDED(nsr))) {
+    fail("TestNotification: CompleteMove");
+    return PR_FALSE;
+  }
+
+  mImpl->mSelChanged = PR_FALSE;
+  nsr = selCon->CharacterMove(PR_TRUE, PR_FALSE);
+  if (!(NS_SUCCEEDED(nsr) &&
+        mImpl->mSelChanged)) {
+    fail("TestNotification: CharacterMove");
+    return PR_FALSE;
+  }
+
+  mImpl->mSelChanged = PR_FALSE;
+  nsr = selCon->CharacterMove(PR_TRUE, PR_TRUE);
+  if (!(NS_SUCCEEDED(nsr) &&
+        mImpl->mSelChanged)) {
+    fail("TestNotification: CharacterMove (extend)");
+    return PR_FALSE;
+  }
+
+  nsCOMPtr<nsIWidget> widget;
+  nsCOMPtr<nsIDocShell> docShell;
+  nsr = mWindow->GetDocShell(getter_AddRefs(docShell));
+  if (NS_SUCCEEDED(nsr) && docShell) {
+    nsCOMPtr<nsIPresShell> presShell;
+    nsr = docShell->GetPresShell(getter_AddRefs(presShell));
+    if (NS_SUCCEEDED(nsr) && presShell) {
+      nsCOMPtr<nsIViewManager> viewManager = presShell->GetViewManager();
+      if (viewManager) {
+        nsr = viewManager->GetWidget(getter_AddRefs(widget));
+      }
+    }
+  }
+  if (!(NS_SUCCEEDED(nsr) && widget)) {
+    fail("TestNotification: get nsIWidget");
+    return PR_FALSE;
+  }
+
+  NS_NAMED_LITERAL_STRING(character, "");
+  NS_NAMED_LITERAL_STRING(characterA, "A");
+
+  // The selection test code above placed the selection at offset 1 to 2
+  const LONG TEXTCHANGE1_START  = 1;
+  const LONG TEXTCHANGE1_OLDEND = 2;
+  const LONG TEXTCHANGE1_NEWEND = 2;
+
+  // replace single selected character with 'A'
+  if (!TestNotificationTextChange(widget, 'A', characterA,
+        TEXTCHANGE1_START, TEXTCHANGE1_OLDEND, TEXTCHANGE1_NEWEND)) {
+    fail("TestNotification: text change 1");
+    return PR_FALSE;
+  }
+
+  const LONG TEXTCHANGE2_START  = TEXTCHANGE1_NEWEND;
+  const LONG TEXTCHANGE2_OLDEND = TEXTCHANGE1_NEWEND;
+  const LONG TEXTCHANGE2_NEWEND = TEXTCHANGE1_NEWEND + 1;
+
+  // insert 'A'
+  if (!TestNotificationTextChange(widget, 'A', characterA,
+        TEXTCHANGE2_START, TEXTCHANGE2_OLDEND, TEXTCHANGE2_NEWEND)) {
+    fail("TestNotification: text change 2");
+    return PR_FALSE;
+  }
+
+  const LONG TEXTCHANGE3_START  = TEXTCHANGE2_NEWEND - 1;
+  const LONG TEXTCHANGE3_OLDEND = TEXTCHANGE2_NEWEND;
+  const LONG TEXTCHANGE3_NEWEND = TEXTCHANGE2_NEWEND - 1;
+
+  // backspace
+  if (!TestNotificationTextChange(widget, '\b', character,
+        TEXTCHANGE3_START, TEXTCHANGE3_OLDEND, TEXTCHANGE3_NEWEND)) {
+    fail("TestNotification: text change 3");
+    return PR_FALSE;
+  }
+  return PR_TRUE;
+}
+
+nsresult
+TestApp::GetSelCon(nsISelectionController** aSelCon)
+{
+  nsCOMPtr<nsIDocShell> docShell;
+  nsresult nsr = mWindow->GetDocShell(getter_AddRefs(docShell));
+  if (NS_SUCCEEDED(nsr) && docShell) {
+    nsCOMPtr<nsIPresShell> presShell;
+    nsr = docShell->GetPresShell(getter_AddRefs(presShell));
+    if (NS_SUCCEEDED(nsr) && presShell) {
+      nsIFrame* frame = presShell->GetPrimaryFrameFor(
+          nsCOMPtr<nsIContent>(do_QueryInterface(mCurrentNode)));
+      if (frame) {
+        nsPresContext* presContext = presShell->GetPresContext();
+        if (presContext) {
+          nsr = frame->GetSelectionController(presContext, aSelCon);
+        }
+      }
+    }
+  }
+  return nsr;
+}
+
+int main(int argc, char** argv)
+{
+  ScopedXPCOM xpcom("TestWinTSF (bug #88831)");
+  if (xpcom.failed())
+    return 1;
+
+  nsRefPtr<TestApp> tests = new TestApp();
+  if (!tests)
+    return 1;
+
+  if (NS_FAILED(tests->Run())) {
+    fail("run failed");
+    return 1;
+  }
+  return int(tests->CheckFailed());
+}