--- 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,
+ ¤tSel, &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());
+}