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 idunknown
push userunknown
push dateunknown
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());
+}