From bb7a62d1cf35a2d401a722efa34465b208aa3eaa Mon Sep 17 00:00:00 2001 From: Masayuki Nakano Date: Fri, 19 Mar 2010 14:02:53 +0900 Subject: [PATCH] Bug 528396 Create XP level IME transaction tests r=roc+mats, sr=jst --- content/events/src/nsContentEventHandler.cpp | 101 +- content/events/src/nsContentEventHandler.h | 4 + dom/base/Makefile.in | 1 + dom/base/nsDOMWindowUtils.cpp | 241 +++++ dom/base/nsQueryContentEventResult.cpp | 184 ++++ dom/base/nsQueryContentEventResult.h | 65 ++ dom/interfaces/base/Makefile.in | 1 + dom/interfaces/base/nsIDOMWindowUtils.idl | 172 ++- .../base/nsIQueryContentEventResult.idl | 61 ++ layout/base/nsLayoutUtils.cpp | 26 + layout/base/nsLayoutUtils.h | 8 + layout/base/nsPresShell.cpp | 29 +- .../mochitest/tests/SimpleTest/EventUtils.js | 258 +++++ widget/tests/Makefile.in | 2 + widget/tests/TestWinTSF.cpp | 146 +-- .../test_composition_text_querycontent.xul | 30 + .../window_composition_text_querycontent.xul | 986 ++++++++++++++++++ 17 files changed, 2126 insertions(+), 189 deletions(-) create mode 100644 dom/base/nsQueryContentEventResult.cpp create mode 100644 dom/base/nsQueryContentEventResult.h create mode 100644 dom/interfaces/base/nsIQueryContentEventResult.idl create mode 100644 widget/tests/test_composition_text_querycontent.xul create mode 100644 widget/tests/window_composition_text_querycontent.xul diff --git a/content/events/src/nsContentEventHandler.cpp b/content/events/src/nsContentEventHandler.cpp index 70127ca5429..80d0f0542af 100644 --- a/content/events/src/nsContentEventHandler.cpp +++ b/content/events/src/nsContentEventHandler.cpp @@ -77,17 +77,12 @@ nsContentEventHandler::nsContentEventHandler( } nsresult -nsContentEventHandler::Init(nsQueryContentEvent* aEvent) +nsContentEventHandler::InitCommon() { - NS_ASSERTION(aEvent, "aEvent must not be null"); - if (mSelection) return NS_OK; - aEvent->mSucceeded = PR_FALSE; - - if (!mPresShell) - return NS_ERROR_NOT_AVAILABLE; + NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_AVAILABLE); // If text frame which has overflowing selection underline is dirty, // we need to flush the pending reflow here. @@ -99,11 +94,6 @@ nsContentEventHandler::Init(nsQueryContentEvent* aEvent) NS_ASSERTION(mSelection, "GetSelectionForCopy succeeded, but the result is null"); - PRBool isCollapsed; - rv = mSelection->GetIsCollapsed(&isCollapsed); - if (NS_FAILED(rv)) - return NS_ERROR_NOT_AVAILABLE; - aEvent->mReply.mHasSelection = !isCollapsed; nsCOMPtr firstRange; rv = mSelection->GetRangeAt(0, getter_AddRefs(firstRange)); @@ -126,9 +116,26 @@ nsContentEventHandler::Init(nsQueryContentEvent* aEvent) mRootContent = startNode->GetSelectionRootContent(mPresShell); NS_ENSURE_TRUE(mRootContent, NS_ERROR_FAILURE); + return NS_OK; +} + +nsresult +nsContentEventHandler::Init(nsQueryContentEvent* aEvent) +{ + NS_ASSERTION(aEvent, "aEvent must not be null"); + + nsresult rv = InitCommon(); + NS_ENSURE_SUCCESS(rv, rv); + + aEvent->mSucceeded = PR_FALSE; aEvent->mReply.mContentsRoot = mRootContent.get(); + PRBool isCollapsed; + rv = mSelection->GetIsCollapsed(&isCollapsed); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_AVAILABLE); + aEvent->mReply.mHasSelection = !isCollapsed; + nsRefPtr caret; rv = mPresShell->GetCaret(getter_AddRefs(caret)); NS_ENSURE_SUCCESS(rv, rv); @@ -143,6 +150,19 @@ nsContentEventHandler::Init(nsQueryContentEvent* aEvent) return NS_OK; } +nsresult +nsContentEventHandler::Init(nsSelectionEvent* aEvent) +{ + NS_ASSERTION(aEvent, "aEvent must not be null"); + + nsresult rv = InitCommon(); + NS_ENSURE_SUCCESS(rv, rv); + + aEvent->mSucceeded = PR_FALSE; + + 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, @@ -664,9 +684,8 @@ nsContentEventHandler::OnQueryCaretRect(nsQueryContentEvent* aEvent) nsIFrame* caretFrame = caret->GetGeometry(mSelection, &rect); if (!caretFrame) return NS_ERROR_FAILURE; - nsPoint windowOffset(0, 0); - caretFrame->GetWindowOffset(windowOffset); - rect.MoveBy(windowOffset); + rv = ConvertToRootViewRelativeOffset(caretFrame, rect); + NS_ENSURE_SUCCESS(rv, rv); aEvent->mReply.mRect = rect.ToOutsidePixels(caretFrame->PresContext()->AppUnitsPerDevPixel()); aEvent->mSucceeded = PR_TRUE; @@ -747,8 +766,32 @@ nsContentEventHandler::OnQueryCharacterAtPoint(nsQueryContentEvent* aEvent) return rv; nsIFrame* rootFrame = mPresShell->GetRootFrame(); + NS_ENSURE_TRUE(rootFrame, NS_ERROR_FAILURE); + nsIWidget* rootWidget = rootFrame->GetWindow(); + NS_ENSURE_TRUE(rootWidget, NS_ERROR_FAILURE); + + // The root frame's widget might be different, e.g., the event was fired on + // a popup but the rootFrame is the document root. + if (rootWidget != aEvent->widget) { + NS_PRECONDITION(aEvent->widget, "The event must have the widget"); + nsIView* view = nsIView::GetViewFor(aEvent->widget); + NS_ENSURE_TRUE(view, NS_ERROR_FAILURE); + rootFrame = nsLayoutUtils::GetFrameFor(view); + NS_ENSURE_TRUE(rootFrame, NS_ERROR_FAILURE); + rootWidget = rootFrame->GetWindow(); + NS_ENSURE_TRUE(rootWidget, NS_ERROR_FAILURE); + } + + nsQueryContentEvent eventOnRoot(PR_TRUE, NS_QUERY_CHARACTER_AT_POINT, + rootWidget); + eventOnRoot.refPoint = aEvent->refPoint; + if (rootWidget != aEvent->widget) { + eventOnRoot.refPoint += aEvent->widget->WidgetToScreenOffset(); + eventOnRoot.refPoint -= rootWidget->WidgetToScreenOffset(); + } nsPoint ptInRoot = - nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, rootFrame); + nsLayoutUtils::GetEventCoordinatesRelativeTo(&eventOnRoot, rootFrame); + nsIFrame* targetFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, ptInRoot); if (!targetFrame || targetFrame->GetType() != nsGkAtoms::textFrame) { // there is no character at the point. @@ -882,11 +925,17 @@ nsContentEventHandler::OnSelectionEvent(nsSelectionEvent* aEvent) aEvent->mSucceeded = PR_FALSE; // Get selection to manipulate - nsCOMPtr sel; + // XXX why do we need to get them from ISM? This method should work fine + // without ISM. nsresult rv = nsIMEStateManager:: - GetFocusSelectionAndRoot(getter_AddRefs(sel), + GetFocusSelectionAndRoot(getter_AddRefs(mSelection), getter_AddRefs(mRootContent)); - NS_ENSURE_SUCCESS(rv, rv); + if (rv != NS_ERROR_NOT_AVAILABLE) { + NS_ENSURE_SUCCESS(rv, rv); + } else { + rv = Init(aEvent); + NS_ENSURE_SUCCESS(rv, rv); + } // Get range from offset and length nsRefPtr range = new nsRange(); @@ -906,32 +955,32 @@ nsContentEventHandler::OnSelectionEvent(nsSelectionEvent* aEvent) nsCOMPtr endDomNode(do_QueryInterface(endNode)); NS_ENSURE_TRUE(startDomNode && endDomNode, NS_ERROR_UNEXPECTED); - nsCOMPtr selPrivate = do_QueryInterface(sel); + nsCOMPtr selPrivate = do_QueryInterface(mSelection); NS_ENSURE_TRUE(selPrivate, NS_ERROR_UNEXPECTED); selPrivate->StartBatchChanges(); // Clear selection first before setting - rv = sel->RemoveAllRanges(); + rv = mSelection->RemoveAllRanges(); // Need to call EndBatchChanges at the end even if call failed if (NS_SUCCEEDED(rv)) { if (aEvent->mReversed) { - rv = sel->Collapse(endDomNode, endOffset); + rv = mSelection->Collapse(endDomNode, endOffset); } else { - rv = sel->Collapse(startDomNode, startOffset); + rv = mSelection->Collapse(startDomNode, startOffset); } if (NS_SUCCEEDED(rv) && (startDomNode != endDomNode || startOffset != endOffset)) { if (aEvent->mReversed) { - rv = sel->Extend(startDomNode, startOffset); + rv = mSelection->Extend(startDomNode, startOffset); } else { - rv = sel->Extend(endDomNode, endOffset); + rv = mSelection->Extend(endDomNode, endOffset); } } } selPrivate->EndBatchChanges(); NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr(do_QueryInterface(sel))->ScrollIntoView( + nsCOMPtr(do_QueryInterface(mSelection))->ScrollIntoView( nsISelectionController::SELECTION_FOCUS_REGION, PR_FALSE, -1, -1); aEvent->mSucceeded = PR_TRUE; return NS_OK; diff --git a/content/events/src/nsContentEventHandler.h b/content/events/src/nsContentEventHandler.h index 44de920c7f5..493dcc91fe5 100644 --- a/content/events/src/nsContentEventHandler.h +++ b/content/events/src/nsContentEventHandler.h @@ -95,6 +95,10 @@ protected: nsCOMPtr mRootContent; nsresult Init(nsQueryContentEvent* aEvent); + nsresult Init(nsSelectionEvent* aEvent); + + // InitCommon() is called from each Init(). + nsresult InitCommon(); public: // FlatText means the text that is generated from DOM tree. The BR elements diff --git a/dom/base/Makefile.in b/dom/base/Makefile.in index bcf8c23176a..ff6b843c5e5 100644 --- a/dom/base/Makefile.in +++ b/dom/base/Makefile.in @@ -99,6 +99,7 @@ CPPSRCS = \ nsDOMClassInfo.cpp \ nsScriptNameSpaceManager.cpp \ nsDOMScriptObjectFactory.cpp \ + nsQueryContentEventResult.cpp \ $(NULL) include $(topsrcdir)/dom/dom-config.mk diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index b74539cf762..5c3e419beb3 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -42,6 +42,7 @@ #include "nsIDOMNSEvent.h" #include "nsIPrivateDOMEvent.h" #include "nsDOMWindowUtils.h" +#include "nsQueryContentEventResult.h" #include "nsGlobalWindow.h" #include "nsIDocument.h" #include "nsFocusManager.h" @@ -50,6 +51,7 @@ #include "nsIScrollableFrame.h" #include "nsContentUtils.h" +#include "nsLayoutUtils.h" #include "nsIFrame.h" #include "nsIWidget.h" @@ -70,6 +72,15 @@ #include #endif +static PRBool IsUniversalXPConnectCapable() +{ + PRBool hasCap = PR_FALSE; + nsresult rv = nsContentUtils::GetSecurityManager()-> + IsCapabilityEnabled("UniversalXPConnect", &hasCap); + NS_ENSURE_SUCCESS(rv, PR_FALSE); + return hasCap; +} + NS_INTERFACE_MAP_BEGIN(nsDOMWindowUtils) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMWindowUtils) NS_INTERFACE_MAP_ENTRY(nsIDOMWindowUtils) @@ -918,6 +929,236 @@ nsDOMWindowUtils::DispatchDOMEventViaPresShell(nsIDOMNode* aTarget, return NS_OK; } +static void +InitEvent(nsGUIEvent &aEvent, nsIntPoint *aPt = nsnull) +{ + if (aPt) { + aEvent.refPoint = *aPt; + } + aEvent.time = PR_IntervalNow(); +} + +NS_IMETHODIMP +nsDOMWindowUtils::SendCompositionEvent(const nsAString& aType) +{ + if (!IsUniversalXPConnectCapable()) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + // get the widget to send the event to + nsCOMPtr widget = GetWidget(); + if (!widget) { + return NS_ERROR_FAILURE; + } + + PRUint32 msg; + if (aType.EqualsLiteral("compositionstart")) { + msg = NS_COMPOSITION_START; + } else if (aType.EqualsLiteral("compositionend")) { + msg = NS_COMPOSITION_END; + } else { + return NS_ERROR_FAILURE; + } + + nsCompositionEvent compositionEvent(PR_TRUE, msg, widget); + InitEvent(compositionEvent); + + nsEventStatus status; + nsresult rv = widget->DispatchEvent(&compositionEvent, status); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +static void +AppendClause(PRInt32 aClauseLength, PRUint32 aClauseAttr, + nsTArray* aRanges) +{ + NS_PRECONDITION(aRanges, "aRange is null"); + if (aClauseLength == 0) { + return; + } + nsTextRange range; + range.mStartOffset = aRanges->Length() == 0 ? 0 : + aRanges->ElementAt(aRanges->Length() - 1).mEndOffset + 1; + range.mEndOffset = range.mStartOffset + aClauseLength; + NS_ASSERTION(range.mStartOffset <= range.mEndOffset, "range is invalid"); + NS_PRECONDITION(aClauseAttr == NS_TEXTRANGE_RAWINPUT || + aClauseAttr == NS_TEXTRANGE_SELECTEDRAWTEXT || + aClauseAttr == NS_TEXTRANGE_CONVERTEDTEXT || + aClauseAttr == NS_TEXTRANGE_SELECTEDCONVERTEDTEXT, + "aClauseAttr is invalid value"); + range.mRangeType = aClauseAttr; + aRanges->AppendElement(range); +} + +NS_IMETHODIMP +nsDOMWindowUtils::SendTextEvent(const nsAString& aCompositionString, + PRInt32 aFirstClauseLength, + PRUint32 aFirstClauseAttr, + PRInt32 aSecondClauseLength, + PRUint32 aSecondClauseAttr, + PRInt32 aThirdClauseLength, + PRUint32 aThirdClauseAttr, + PRInt32 aCaretStart, + PRInt32 aCaretLength) +{ + if (!IsUniversalXPConnectCapable()) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + // get the widget to send the event to + nsCOMPtr widget = GetWidget(); + if (!widget) { + return NS_ERROR_FAILURE; + } + + nsTextEvent textEvent(PR_TRUE, NS_TEXT_TEXT, widget); + InitEvent(textEvent); + + nsAutoTArray textRanges; + NS_ENSURE_TRUE(aFirstClauseLength >= 0, NS_ERROR_INVALID_ARG); + NS_ENSURE_TRUE(aSecondClauseLength >= 0, NS_ERROR_INVALID_ARG); + NS_ENSURE_TRUE(aThirdClauseLength >= 0, NS_ERROR_INVALID_ARG); + AppendClause(aFirstClauseLength, aFirstClauseAttr, &textRanges); + AppendClause(aSecondClauseLength, aSecondClauseAttr, &textRanges); + AppendClause(aThirdClauseLength, aThirdClauseAttr, &textRanges); + PRInt32 len = aFirstClauseLength + aSecondClauseLength + aThirdClauseLength; + NS_ENSURE_TRUE(len == 0 || len == aCompositionString.Length(), + NS_ERROR_FAILURE); + + if (aCaretStart >= 0) { + nsTextRange range; + range.mStartOffset = aCaretStart; + range.mEndOffset = range.mStartOffset + aCaretLength; + range.mRangeType = NS_TEXTRANGE_CARETPOSITION; + textRanges.AppendElement(range); + } + + textEvent.theText = aCompositionString; + + textEvent.rangeCount = textRanges.Length(); + textEvent.rangeArray = textRanges.Elements(); + + nsEventStatus status; + nsresult rv = widget->DispatchEvent(&textEvent, status); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWindowUtils::SendQueryContentEvent(PRUint32 aType, + PRUint32 aOffset, PRUint32 aLength, + PRInt32 aX, PRInt32 aY, + nsIQueryContentEventResult **aResult) +{ + *aResult = nsnull; + + if (!IsUniversalXPConnectCapable()) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + // get the widget to send the event to + nsCOMPtr widget = GetWidget(); + if (!widget) { + return NS_ERROR_FAILURE; + } + + if (aType != NS_QUERY_SELECTED_TEXT && + aType != NS_QUERY_TEXT_CONTENT && + aType != NS_QUERY_CARET_RECT && + aType != NS_QUERY_TEXT_RECT && + aType != NS_QUERY_EDITOR_RECT && + aType != NS_QUERY_CHARACTER_AT_POINT) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr targetWidget = widget; + nsIntPoint pt(aX, aY); + + if (aType == QUERY_CHARACTER_AT_POINT) { + // Looking for the widget at the point. + nsQueryContentEvent dummyEvent(PR_TRUE, NS_QUERY_CONTENT_STATE, widget); + InitEvent(dummyEvent, &pt); + nsIFrame* popupFrame = + nsLayoutUtils::GetPopupFrameForEventCoordinates(&dummyEvent); + + nsIntRect widgetBounds; + nsresult rv = widget->GetClientBounds(widgetBounds); + + // There is no popup frame at the point and the point isn't in our widget, + // we cannot process this request. + NS_ENSURE_TRUE(popupFrame || widgetBounds.Contains(pt), + NS_ERROR_FAILURE); + + // Fire the event on the widget at the point + if (popupFrame) { + targetWidget = popupFrame->GetWindow(); + } + } + + pt += widget->WidgetToScreenOffset() - targetWidget->WidgetToScreenOffset(); + + nsQueryContentEvent queryEvent(PR_TRUE, aType, targetWidget); + InitEvent(queryEvent, &pt); + + switch (aType) { + case NS_QUERY_TEXT_CONTENT: + queryEvent.InitForQueryTextContent(aOffset, aLength); + break; + case NS_QUERY_CARET_RECT: + queryEvent.InitForQueryCaretRect(aOffset); + break; + case NS_QUERY_TEXT_RECT: + queryEvent.InitForQueryTextRect(aOffset, aLength); + break; + } + + nsEventStatus status; + nsresult rv = targetWidget->DispatchEvent(&queryEvent, status); + NS_ENSURE_SUCCESS(rv, rv); + + nsQueryContentEventResult* result = new nsQueryContentEventResult(); + NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY); + result->SetEventResult(widget, queryEvent); + NS_ADDREF(*aResult = result); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWindowUtils::SendSelectionSetEvent(PRUint32 aOffset, + PRUint32 aLength, + PRBool aReverse, + PRBool *aResult) +{ + *aResult = PR_FALSE; + + if (!IsUniversalXPConnectCapable()) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + // get the widget to send the event to + nsCOMPtr widget = GetWidget(); + if (!widget) { + return NS_ERROR_FAILURE; + } + + nsSelectionEvent selectionEvent(PR_TRUE, NS_SELECTION_SET, widget); + InitEvent(selectionEvent); + + selectionEvent.mOffset = aOffset; + selectionEvent.mLength = aLength; + selectionEvent.mReversed = aReverse; + + nsEventStatus status; + nsresult rv = widget->DispatchEvent(&selectionEvent, status); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = selectionEvent.mSucceeded; + return NS_OK; +} + NS_IMETHODIMP nsDOMWindowUtils::SendContentCommandEvent(const nsAString& aType, nsITransferable * aTransferable) diff --git a/dom/base/nsQueryContentEventResult.cpp b/dom/base/nsQueryContentEventResult.cpp new file mode 100644 index 00000000000..a3b4c9caf9b --- /dev/null +++ b/dom/base/nsQueryContentEventResult.cpp @@ -0,0 +1,184 @@ +/* -*- 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 + * Mozilla Japan. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Masayuki Nakano + * + * 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 "nsQueryContentEventResult.h" +#include "nsGUIEvent.h" +#include "nsIWidget.h" +#include "nsPoint.h" + +NS_INTERFACE_MAP_BEGIN(nsQueryContentEventResult) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIQueryContentEventResult) + NS_INTERFACE_MAP_ENTRY(nsIQueryContentEventResult) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsQueryContentEventResult) +NS_IMPL_RELEASE(nsQueryContentEventResult) + +nsQueryContentEventResult::nsQueryContentEventResult() : + mEventID(0), mSucceeded(PR_FALSE) +{ +} + +nsQueryContentEventResult::~nsQueryContentEventResult() +{ +} + +NS_IMETHODIMP +nsQueryContentEventResult::GetOffset(PRUint32 *aOffset) +{ + PRBool notFound; + nsresult rv = GetNotFound(¬Found); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(!notFound, NS_ERROR_NOT_AVAILABLE); + *aOffset = mOffset; + return NS_OK; +} + +static PRBool IsRectEnabled(PRUint32 aEventID) +{ + return aEventID == NS_QUERY_CARET_RECT || + aEventID == NS_QUERY_TEXT_RECT || + aEventID == NS_QUERY_EDITOR_RECT || + aEventID == NS_QUERY_CHARACTER_AT_POINT; +} + +NS_IMETHODIMP +nsQueryContentEventResult::GetReversed(PRBool *aReversed) +{ + NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE); + NS_ENSURE_TRUE(mEventID == NS_QUERY_SELECTED_TEXT, + NS_ERROR_NOT_AVAILABLE); + *aReversed = mReversed; + return NS_OK; +} + +NS_IMETHODIMP +nsQueryContentEventResult::GetLeft(PRInt32 *aLeft) +{ + NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE); + NS_ENSURE_TRUE(IsRectEnabled(mEventID), + NS_ERROR_NOT_AVAILABLE); + *aLeft = mRect.x; + return NS_OK; +} + +NS_IMETHODIMP +nsQueryContentEventResult::GetWidth(PRInt32 *aWidth) +{ + NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE); + NS_ENSURE_TRUE(IsRectEnabled(mEventID), + NS_ERROR_NOT_AVAILABLE); + *aWidth = mRect.width; + return NS_OK; +} + +NS_IMETHODIMP +nsQueryContentEventResult::GetTop(PRInt32 *aTop) +{ + NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE); + NS_ENSURE_TRUE(IsRectEnabled(mEventID), + NS_ERROR_NOT_AVAILABLE); + *aTop = mRect.y; + return NS_OK; +} + +NS_IMETHODIMP +nsQueryContentEventResult::GetHeight(PRInt32 *aHeight) +{ + NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE); + NS_ENSURE_TRUE(IsRectEnabled(mEventID), + NS_ERROR_NOT_AVAILABLE); + *aHeight = mRect.height; + return NS_OK; +} + +NS_IMETHODIMP +nsQueryContentEventResult::GetText(nsAString &aText) +{ + NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE); + NS_ENSURE_TRUE(mEventID == NS_QUERY_SELECTED_TEXT || + mEventID == NS_QUERY_TEXT_CONTENT, + NS_ERROR_NOT_AVAILABLE); + aText = mString; + return NS_OK; +} + +NS_IMETHODIMP +nsQueryContentEventResult::GetSucceeded(PRBool *aSucceeded) +{ + NS_ENSURE_TRUE(mEventID != 0, NS_ERROR_NOT_INITIALIZED); + *aSucceeded = mSucceeded; + return NS_OK; +} + +NS_IMETHODIMP +nsQueryContentEventResult::GetNotFound(PRBool *aNotFound) +{ + NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE); + NS_ENSURE_TRUE(mEventID == NS_QUERY_SELECTED_TEXT || + mEventID == NS_QUERY_CHARACTER_AT_POINT, + NS_ERROR_NOT_AVAILABLE); + *aNotFound = (mOffset == nsQueryContentEvent::NOT_FOUND); + return NS_OK; +} + +void +nsQueryContentEventResult::SetEventResult(nsIWidget* aWidget, + const nsQueryContentEvent &aEvent) +{ + mEventID = aEvent.message; + mSucceeded = aEvent.mSucceeded; + mReversed = aEvent.mReply.mReversed; + mRect = aEvent.mReply.mRect; + mOffset = aEvent.mReply.mOffset; + mString = aEvent.mReply.mString; + + if (!IsRectEnabled(mEventID) || !aWidget || !mSucceeded) { + return; + } + + nsIWidget* topWidget = aWidget->GetTopLevelWidget(); + if (!topWidget || topWidget == aWidget) { + return; + } + + // Convert the top widget related coordinates to the given widget's. + nsIntPoint offset = + aWidget->WidgetToScreenOffset() - topWidget->WidgetToScreenOffset(); + mRect.MoveBy(-offset); +} diff --git a/dom/base/nsQueryContentEventResult.h b/dom/base/nsQueryContentEventResult.h new file mode 100644 index 00000000000..e07355c2028 --- /dev/null +++ b/dom/base/nsQueryContentEventResult.h @@ -0,0 +1,65 @@ +/* -*- 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 + * Mozilla Japan. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Masayuki Nakano + * + * 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 "nsIQueryContentEventResult.h" +#include "nsString.h" +#include "nsRect.h" + +class nsQueryContentEvent; +class nsIWidget; + +class nsQueryContentEventResult : public nsIQueryContentEventResult +{ +public: + nsQueryContentEventResult(); + ~nsQueryContentEventResult(); + NS_DECL_ISUPPORTS + NS_DECL_NSIQUERYCONTENTEVENTRESULT + + void SetEventResult(nsIWidget* aWidget, const nsQueryContentEvent &aEvent); + +protected: + PRUint32 mEventID; + + PRUint32 mOffset; + nsString mString; + nsIntRect mRect; + + PRPackedBool mSucceeded; + PRPackedBool mReversed; +}; diff --git a/dom/interfaces/base/Makefile.in b/dom/interfaces/base/Makefile.in index 6a4fa8fb30e..59faaf56956 100644 --- a/dom/interfaces/base/Makefile.in +++ b/dom/interfaces/base/Makefile.in @@ -80,6 +80,7 @@ XPIDLSRCS = \ nsIDOMClientRect.idl \ nsIDOMClientRectList.idl \ nsIFocusManager.idl \ + nsIQueryContentEventResult.idl \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl index d72c92928bc..0b8674c9ba8 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -50,8 +50,9 @@ interface nsIDOMElement; interface nsIDOMHTMLCanvasElement; interface nsIDOMEvent; interface nsITransferable; +interface nsIQueryContentEventResult; -[scriptable, uuid(5ab44028-20ed-499a-bbe4-1805a1f350c8)] +[scriptable, uuid(00ca8d4f-61f1-4d9c-a7c1-82651b0cf02b)] interface nsIDOMWindowUtils : nsISupports { /** @@ -443,4 +444,173 @@ interface nsIDOMWindowUtils : nsISupports { */ void sendContentCommandEvent(in AString aType, [optional] in nsITransferable aTransferable); + + /** + * Synthesize a composition event to the window. + * + * Cannot be accessed from unprivileged context (not content-accessible) + * Will throw a DOM security error if called without UniversalXPConnect + * privileges. + * + * @param aType The event type: "compositionstart" or "compositionend". + */ + void sendCompositionEvent(in AString aType); + + /** + * Synthesize a text event to the window. + * + * Cannot be accessed from unprivileged context (not content-accessible) + * Will throw a DOM security error if called without UniversalXPConnect + * privileges. + * + * Currently, this method doesn't support 4 or more clauses composition + * string. + * + * @param aCompositionString composition string + * @param a*ClauseLengh the length of nth clause, set 0 when you + * don't need second or third clause. + * @param a*ClauseAttr the attribute of nth clause, uese following + * const values. + * @param aCaretStart the caret position in the composition string, + * if you set negative value, this method don't + * set the caret position to the event. + * @param aCaretLength the caret length, if this is one or more, + * the caret will be wide caret, otherwise, + * it's collapsed. + * XXX nsEditor doesn't support wide caret yet. + */ + + // NOTE: These values must be same to NS_TEXTRANGE_* in nsGUIEvent.h + + const unsigned long COMPOSITION_ATTR_RAWINPUT = 0x02; + const unsigned long COMPOSITION_ATTR_SELECTEDRAWTEXT = 0x03; + const unsigned long COMPOSITION_ATTR_CONVERTEDTEXT = 0x04; + const unsigned long COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05; + + void sendTextEvent(in AString aCompositionString, + in long aFirstClauseLength, + in unsigned long aFirstClauseAttr, + in long aSecondClauseLength, + in unsigned long aSecondClauseAttr, + in long aThirdClauseLength, + in unsigned long aThirdClauseAttr, + in long aCaretStart, + in long aCaretLength); + + /** + * Synthesize a query content event. + * + * @param aType On of the following const values. And see also each comment + * for the other parameters and the result. + */ + nsIQueryContentEventResult sendQueryContentEvent(in unsigned long aType, + in unsigned long aOffset, + in unsigned long aLength, + in long aX, + in long aY); + + // NOTE: following values are same as NS_QUERY_* in nsGUIEvent.h + + /** + * QUERY_SELECTED_TEXT queries the first selection range's information. + * + * @param aOffset Not used. + * @param aLength Not used. + * @param aX Not used. + * @param aY Not used. + * + * @return offset, reversed and text properties of the result are available. + */ + const unsigned long QUERY_SELECTED_TEXT = 3200; + + /** + * QUERY_TEXT_CONTENT queries the text at the specified range. + * + * @param aOffset The first character's offset. 0 is the first character. + * @param aLength The length of getting text. If the aLength is too long, + * the result text is shorter than this value. + * @param aX Not used. + * @param aY Not used. + * + * @return text property of the result is available. + */ + const unsigned long QUERY_TEXT_CONTENT = 3201; + + /** + * QUERY_CARET_RECT queries the (collapsed) caret rect of the offset. + * If the actual caret is there at the specified offset, this returns the + * actual caret rect. Otherwise, this guesses the caret rect from the + * metrics of the text. + * + * @param aOffset The caret offset. 0 is the left side of the first + * caracter in LTR text. + * @param aLength Not used. + * @param aX Not used. + * @param aY Not used. + * + * @return left, top, width and height properties of the result are available. + * The left and the top properties are offset in the client area of + * the DOM window. + */ + const unsigned long QUERY_CARET_RECT = 3203; + + /** + * QUERY_TEXT_RECT queries the specified text's rect. + * + * @param aOffset The first character's offset. 0 is the first character. + * @param aLength The length of getting text. If the aLength is too long, + * the extra length is ignored. + * @param aX Not used. + * @param aY Not used. + * + * @return left, top, width and height properties of the result are available. + * The left and the top properties are offset in the client area of + * the DOM window. + */ + const unsigned long QUERY_TEXT_RECT = 3204; + + /** + * QUERY_TEXT_RECT queries the focused editor's rect. + * + * @param aOffset Not used. + * @param aLength Not used. + * @param aX Not used. + * @param aY Not used. + * + * @return left, top, width and height properties of the result are available. + */ + const unsigned long QUERY_EDITOR_RECT = 3205; + + /** + * QUERY_CHARACTER_AT_POINT queries the character information at the + * specified point. The point is offset in the window. + * NOTE: If there are some panels at the point, this method send the query + * event to the panel's widget automatically. + * + * @param aOffset Not used. + * @param aLength Not used. + * @param aX X offset in the widget. + * @param aY Y offset in the widget. + * + * @return offset, notFound, left, top, width and height properties of the + * result are available. + */ + const unsigned long QUERY_CHARACTER_AT_POINT = 3208; + + /** + * Synthesize a selection set event to the window. + * + * This sets the selection as the specified information. + * + * @param aOffset The caret offset of the selection start. + * @param aLength The length of the selection. If this is too long, the + * extra length is ignored. + * @param aReverse If true, the selection set from |aOffset + aLength| to + * |aOffset|. Otherwise, set from |aOffset| to + * |aOffset + aLength|. + * @return True, if succeeded. Otherwise, false. + */ + boolean sendSelectionSetEvent(in unsigned long aOffset, + in unsigned long aLength, + in boolean aReverse); }; diff --git a/dom/interfaces/base/nsIQueryContentEventResult.idl b/dom/interfaces/base/nsIQueryContentEventResult.idl new file mode 100644 index 00000000000..35b2d8a91a9 --- /dev/null +++ b/dom/interfaces/base/nsIQueryContentEventResult.idl @@ -0,0 +1,61 @@ +/* -*- Mode: IDL; 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 + * Mozilla Japan. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Masayuki Nakano + * + * 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 "nsISupports.idl" + +/** + * The result of query content events. succeeded propery can be used always. + * Whether other properties can be used or not depends on the event. + * See nsIDOMWindowUtils.idl, which properites can be used was documented. + */ + +[scriptable, uuid(4b4ba266-b51e-4f0f-8d0e-9f13cb2a0056)] +interface nsIQueryContentEventResult : nsISupports +{ + readonly attribute unsigned long offset; + readonly attribute boolean reversed; + + readonly attribute long left; + readonly attribute long top; + readonly attribute long width; + readonly attribute long height; + readonly attribute AString text; + + readonly attribute boolean succeeded; + readonly attribute boolean notFound; +}; diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 154e5c84549..4e2be876e49 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -98,6 +98,10 @@ #include "nsSVGOuterSVGFrame.h" #endif +#ifdef MOZ_XUL +#include "nsXULPopupManager.h" +#endif + using namespace mozilla::layers; /** @@ -779,6 +783,28 @@ nsLayoutUtils::GetEventCoordinatesRelativeTo(const nsEvent* aEvent, nsIFrame* aF return widgetToView - aFrame->GetOffsetTo(rootFrame); } +nsIFrame* +nsLayoutUtils::GetPopupFrameForEventCoordinates(const nsEvent* aEvent) +{ +#ifdef MOZ_XUL + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (!pm) { + return nsnull; + } + nsTArray popups = pm->GetVisiblePopups(); + PRUint32 i; + // Search from top to bottom + for (i = 0; i < popups.Length(); i++) { + nsIFrame* popup = popups[i]; + if (popup->GetOverflowRect().Contains( + GetEventCoordinatesRelativeTo(aEvent, popup))) { + return popup; + } + } +#endif + return nsnull; +} + gfxMatrix nsLayoutUtils::ChangeMatrixBasis(const gfxPoint &aOrigin, const gfxMatrix &aMatrix) diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index c550943578f..bf080efc0d3 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -388,6 +388,14 @@ public: nsIFrame* aFrame); /** + * Get the popup frame of a given native mouse event. + * @param aEvent the event. + * @return Null, if there is no popup frame at the point, otherwise, + * returns top-most popup frame at the point. + */ + static nsIFrame* GetPopupFrameForEventCoordinates(const nsEvent* aEvent); + +/** * Translate from widget coordinates to the view's coordinates * @param aPresContext the PresContext for the view * @param aWidget the widget diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 391d5a9eb23..e3b815d76f1 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -6156,27 +6156,16 @@ PresShell::HandleEvent(nsIView *aView, // list. if (framePresContext == rootPresContext && frame == FrameManager()->GetRootFrame()) { - -#ifdef MOZ_XUL - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) { - nsTArray popups = pm->GetVisiblePopups(); - PRUint32 i; - // Search from top to bottom - nsIDocument* doc = framePresContext->GetPresShell()->GetDocument(); - for (i = 0; i < popups.Length(); i++) { - nsIFrame* popup = popups[i]; - if (popup->GetOverflowRect().Contains( - nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, popup)) && - !nsContentUtils::ContentIsCrossDocDescendantOf( - doc, popup->GetContent())) { - // The event should target the popup - frame = popup; - break; - } - } + nsIFrame* popupFrame = + nsLayoutUtils::GetPopupFrameForEventCoordinates(aEvent); + // If the popupFrame is an ancestor of the 'frame', the frame should + // handle the event, otherwise, the popup should handle it. + if (popupFrame && + !nsContentUtils::ContentIsCrossDocDescendantOf( + framePresContext->GetPresShell()->GetDocument(), + popupFrame->GetContent())) { + frame = popupFrame; } -#endif } PRBool captureRetarget = PR_FALSE; diff --git a/testing/mochitest/tests/SimpleTest/EventUtils.js b/testing/mochitest/tests/SimpleTest/EventUtils.js index 14b2ba24c02..988df035f44 100644 --- a/testing/mochitest/tests/SimpleTest/EventUtils.js +++ b/testing/mochitest/tests/SimpleTest/EventUtils.js @@ -546,3 +546,261 @@ function disableNonTestMouseEvents(aDisable) if (utils) utils.disableNonTestMouseEvents(aDisable); } + +function _getDOMWindowUtils(aWindow) +{ + if (!aWindow) { + aWindow = window; + } + return aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor). + getInterface(Components.interfaces.nsIDOMWindowUtils); +} + +/** + * Synthesize a composition event. + * + * @param aIsCompositionStart If true, this synthesize compositionstart event. + * Otherwise, compositionend event. + * @param aWindow Optional (If null, current |window| will be used) + */ +function synthesizeComposition(aIsCompositionStart, aWindow) +{ + netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); + + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return; + } + + utils.sendCompositionEvent(aIsCompositionStart ? + "compositionstart" : "compositionend"); +} + +/** + * Synthesize a text event. + * + * @param aEvent The text event's information, this has |composition| + * and |caret| members. |composition| has |string| and + * |clauses| members. |clauses| must be array object. Each + * object has |length| and |attr|. And |caret| has |start| and + * |length|. See the following tree image. + * + * aEvent + * +-- composition + * | +-- string + * | +-- clauses[] + * | +-- length + * | +-- attr + * +-- caret + * +-- start + * +-- length + * + * Set the composition string to |composition.string|. Set its + * clauses information to the |clauses| array. + * + * When it's composing, set the each clauses' length to the + * |composition.clauses[n].length|. The sum of the all length + * values must be same as the length of |composition.string|. + * Set nsIDOMWindowUtils.COMPOSITION_ATTR_* to the + * |composition.clauses[n].attr|. + * + * When it's not composing, set 0 to the + * |composition.clauses[0].length| and + * |composition.clauses[0].attr|. + * + * Set caret position to the |caret.start|. It's offset from + * the start of the composition string. Set caret length to + * |caret.length|. If it's larger than 0, it should be wide + * caret. However, current nsEditor doesn't support wide + * caret, therefore, you should always set 0 now. + * + * @param aWindow Optional (If null, current |window| will be used) + */ +function synthesizeText(aEvent, aWindow) +{ + netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); + + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return; + } + + if (!aEvent.composition || !aEvent.composition.clauses || + !aEvent.composition.clauses[0]) { + return; + } + + var firstClauseLength = aEvent.composition.clauses[0].length; + var firstClauseAttr = aEvent.composition.clauses[0].attr; + var secondClauseLength = 0; + var secondClauseAttr = 0; + var thirdClauseLength = 0; + var thirdClauseAttr = 0; + if (aEvent.composition.clauses[1]) { + secondClauseLength = aEvent.composition.clauses[1].length; + secondClauseAttr = aEvent.composition.clauses[1].attr; + if (aEvent.composition.clauses[2]) { + thirdClauseLength = aEvent.composition.clauses[2].length; + thirdClauseAttr = aEvent.composition.clauses[2].attr; + } + } + + var caretStart = -1; + var caretLength = 0; + if (aEvent.caret) { + caretStart = aEvent.caret.start; + caretLength = aEvent.caret.length; + } + + utils.sendTextEvent(aEvent.composition.string, + firstClauseLength, firstClauseAttr, + secondClauseLength, secondClauseAttr, + thirdClauseLength, thirdClauseAttr, + caretStart, caretLength); +} + +/** + * Synthesize a query selected text event. + * + * @param aWindow Optional (If null, current |window| will be used) + * @return An nsIQueryContentEventResult object. If this failed, + * the result might be null. + */ +function synthesizeQuerySelectedText(aWindow) +{ + netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); + + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return nsnull; + } + return utils.sendQueryContentEvent(utils.QUERY_SELECTED_TEXT, 0, 0, 0, 0); +} + +/** + * Synthesize a query text content event. + * + * @param aOffset The character offset. 0 means the first character in the + * selection root. + * @param aLength The length of getting text. If the length is too long, + * the extra length is ignored. + * @param aWindow Optional (If null, current |window| will be used) + * @return An nsIQueryContentEventResult object. If this failed, + * the result might be null. + */ +function synthesizeQueryTextContent(aOffset, aLength, aWindow) +{ + netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); + + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return nsnull; + } + return utils.sendQueryContentEvent(utils.QUERY_TEXT_CONTENT, + aOffset, aLength, 0, 0); +} + +/** + * Synthesize a query caret rect event. + * + * @param aOffset The caret offset. 0 means left side of the first character + * in the selection root. + * @param aWindow Optional (If null, current |window| will be used) + * @return An nsIQueryContentEventResult object. If this failed, + * the result might be null. + */ +function synthesizeQueryCaretRect(aOffset, aWindow) +{ + netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); + + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return nsnull; + } + return utils.sendQueryContentEvent(utils.QUERY_CARET_RECT, + aOffset, 0, 0, 0); +} + +/** + * Synthesize a query text rect event. + * + * @param aOffset The character offset. 0 means the first character in the + * selection root. + * @param aLength The length of the text. If the length is too long, + * the extra length is ignored. + * @param aWindow Optional (If null, current |window| will be used) + * @return An nsIQueryContentEventResult object. If this failed, + * the result might be null. + */ +function synthesizeQueryTextRect(aOffset, aLength, aWindow) +{ + netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); + + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return nsnull; + } + return utils.sendQueryContentEvent(utils.QUERY_TEXT_RECT, + aOffset, aLength, 0, 0); +} + +/** + * Synthesize a query editor rect event. + * + * @param aWindow Optional (If null, current |window| will be used) + * @return An nsIQueryContentEventResult object. If this failed, + * the result might be null. + */ +function synthesizeQueryEditorRect(aWindow) +{ + netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); + + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return nsnull; + } + return utils.sendQueryContentEvent(utils.QUERY_EDITOR_RECT, 0, 0, 0, 0); +} + +/** + * Synthesize a character at point event. + * + * @param aX, aY The offset in the client area of the DOM window. + * @param aWindow Optional (If null, current |window| will be used) + * @return An nsIQueryContentEventResult object. If this failed, + * the result might be null. + */ +function synthesizeCharAtPoint(aX, aY, aWindow) +{ + netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); + + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return nsnull; + } + return utils.sendQueryContentEvent(utils.QUERY_CHARACTER_AT_POINT, + 0, 0, aX, aY); +} + +/** + * Synthesize a selection set event. + * + * @param aOffset The character offset. 0 means the first character in the + * selection root. + * @param aLength The length of the text. If the length is too long, + * the extra length is ignored. + * @param aReverse If true, the selection is from |aOffset + aLength| to + * |aOffset|. Otherwise, from |aOffset| to |aOffset + aLength|. + * @param aWindow Optional (If null, current |window| will be used) + * @return True, if succeeded. Otherwise false. + */ +function synthesizeSelectionSet(aOffset, aLength, aReverse, aWindow) +{ + netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); + + var utils = _getDOMWindowUtils(aWindow); + if (!utils) { + return false; + } + return utils.sendSelectionSetEvent(aOffset, aLength, aReverse); +} diff --git a/widget/tests/Makefile.in b/widget/tests/Makefile.in index 93303176806..255d82f1fea 100644 --- a/widget/tests/Makefile.in +++ b/widget/tests/Makefile.in @@ -70,6 +70,8 @@ _CHROME_FILES = test_bug343416.xul \ window_wheeltransaction.xul \ test_imestate.html \ test_plugin_scroll_consistency.html \ + test_composition_text_querycontent.xul \ + window_composition_text_querycontent.xul \ $(NULL) ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa) diff --git a/widget/tests/TestWinTSF.cpp b/widget/tests/TestWinTSF.cpp index 6d92ad854d0..eb5524645a6 100644 --- a/widget/tests/TestWinTSF.cpp +++ b/widget/tests/TestWinTSF.cpp @@ -143,7 +143,6 @@ protected: PRBool TestExtents(void); PRBool TestComposition(void); PRBool TestNotification(void); - PRBool TestContentEvents(void); PRBool TestEditMessages(void); PRBool TestScrollMessages(void); @@ -1157,7 +1156,7 @@ public: PRInt32 mFocusCount; TSFMgrImpl(TestApp* test) : mTestApp(test), mTest(nsnull), mRefCnt(0), - mDeactivated(PR_FALSE), mFocusCount(0) + mDeactivated(PR_FALSE), mFocusedDocument(nsnull), mFocusCount(0) { } @@ -1661,9 +1660,9 @@ TestApp::OnStateChange(nsIWebProgress *aWebProgress, NS_ASSERTION(aStateFlags & nsIWebProgressListener::STATE_IS_WINDOW && aStateFlags & nsIWebProgressListener::STATE_STOP, "wrong state"); if (NS_SUCCEEDED(Init())) { - printf("Testing content events...\n"); - if (TestContentEvents()) - passed("TestContentEvents"); + mCurrentNode = mTextArea; + mTextArea->Focus(); + if (RunTest(&TestApp::TestEditMessages)) passed("TestEditMessages"); if (RunTest(&TestApp::TestScrollMessages)) @@ -2719,143 +2718,6 @@ TestApp::TestNotification(void) return PR_TRUE; } -PRBool -TestApp::TestContentEvents(void) -{ - mTestString = NS_LITERAL_STRING( - "This is a test of the\r\nContent Events"); - // 0123456789012345678901 2 34567890123456 - // 0 1 2 3 - mTextArea->SetValue(mTestString); - mTextArea->Focus(); - - nsCOMPtr widget; - if (!GetWidget(getter_AddRefs(widget))) { - fail("TestContentEvents: get nsIWidget"); - return PR_FALSE; - } - - nsCOMPtr topLevel = widget->GetTopLevelWidget(); - if (!topLevel) { - fail("TestContentEvents: get top level widget"); - return PR_FALSE; - } - - nsIntRect widgetRect, topLevelRect; - nsresult nsr = widget->GetScreenBounds(widgetRect); - if (NS_FAILED(nsr)) { - fail("TestContentEvents: get widget rect"); - return PR_FALSE; - } - nsr = topLevel->GetScreenBounds(topLevelRect); - if (NS_FAILED(nsr)) { - fail("TestContentEvents: get top level widget rect"); - return PR_FALSE; - } - nsIntPoint widgetOffset = widgetRect.TopLeft() - topLevelRect.TopLeft(); - nsEventStatus eventStatus; - PRBool result = PR_TRUE; - - const PRUint32 kNone = nsQueryContentEvent::NOT_FOUND; - PRUint32 testingOffset[] = { 0, 10, 20, 23, 36 }; - PRUint32 leftSideOffset[] = { kNone, 9, 19, kNone, 35 }; - PRUint32 rightSideOffset[] = { 1, 11, kNone, 24, kNone }; - for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(testingOffset); i++) { - nsQueryContentEvent textRect(PR_TRUE, NS_QUERY_TEXT_RECT, widget); - textRect.InitForQueryTextRect(testingOffset[i], 1); - nsr = widget->DispatchEvent(&textRect, eventStatus); - if (NS_FAILED(nsr) || !textRect.mSucceeded || - textRect.mReply.mRect.IsEmpty()) { - fail("TestContentEvents: get text rect"); - return PR_FALSE; - } - nsIntRect &charRect = textRect.mReply.mRect; - charRect.MoveBy(widgetOffset); - // Note that charRect might be inflated at rounding to pixels! - printf("TestContentEvents: testing... i=%lu, pt={ %ld, %ld }, size={ %ld, %ld }\n", - i, charRect.x, charRect.y, charRect.width, charRect.height); - - nsQueryContentEvent charAtPt1(PR_TRUE, NS_QUERY_CHARACTER_AT_POINT, widget); - charAtPt1.refPoint.x = charRect.x + 1; - charAtPt1.refPoint.y = charRect.y + 1; - nsr = widget->DispatchEvent(&charAtPt1, eventStatus); - if (NS_FAILED(nsr) || !charAtPt1.mSucceeded) { - fail(" TestContentEvents: get char at point1"); - return PR_FALSE; - } - printf(" NS_QUERY_CHARACTER_AT_POINT: pt={ %ld, %ld }, offset=%lu, rect={ %ld, %ld, %ld, %ld }\n", - charAtPt1.refPoint.x, charAtPt1.refPoint.y, - charAtPt1.mReply.mOffset, charAtPt1.mReply.mRect.x, - charAtPt1.mReply.mRect.y, charAtPt1.mReply.mRect.width, - charAtPt1.mReply.mRect.height); - if (charAtPt1.mReply.mOffset != testingOffset[i]) { - fail(" TestContentEvents: get char at point1 (wrong offset)"); - result = PR_FALSE; - } else if (charAtPt1.mReply.mRect != textRect.mReply.mRect) { - fail(" TestContentEvents: get char at point1 (rect mismatch)"); - result = PR_FALSE; - } - - nsQueryContentEvent charAtPt2(PR_TRUE, NS_QUERY_CHARACTER_AT_POINT, widget); - charAtPt2.refPoint.x = charRect.XMost() - 2; - charAtPt2.refPoint.y = charRect.YMost() - 2; - nsr = widget->DispatchEvent(&charAtPt2, eventStatus); - if (NS_FAILED(nsr) || !charAtPt2.mSucceeded) { - fail(" TestContentEvents: get char at point2"); - return PR_FALSE; - } - printf(" NS_QUERY_CHARACTER_AT_POINT: pt={ %ld, %ld }, offset=%lu, rect={ %ld, %ld, %ld, %ld }\n", - charAtPt2.refPoint.x, charAtPt2.refPoint.y, - charAtPt2.mReply.mOffset, charAtPt2.mReply.mRect.x, - charAtPt2.mReply.mRect.y, charAtPt2.mReply.mRect.width, - charAtPt2.mReply.mRect.height); - if (charAtPt2.mReply.mOffset != testingOffset[i]) { - fail(" TestContentEvents: get char at point2 (wrong offset)"); - result = PR_FALSE; - } else if (charAtPt2.mReply.mRect != textRect.mReply.mRect) { - fail(" TestContentEvents: get char at point2 (rect mismatch)"); - result = PR_FALSE; - } - - nsQueryContentEvent charAtPt3(PR_TRUE, NS_QUERY_CHARACTER_AT_POINT, widget); - charAtPt3.refPoint.x = charRect.x - 2; - charAtPt3.refPoint.y = charRect.y + 1; - nsr = widget->DispatchEvent(&charAtPt3, eventStatus); - if (NS_FAILED(nsr) || !charAtPt3.mSucceeded) { - fail(" TestContentEvents: get char at point3"); - return PR_FALSE; - } - printf(" NS_QUERY_CHARACTER_AT_POINT: pt={ %ld, %ld }, offset=%lu, rect={ %ld, %ld, %ld, %ld }\n", - charAtPt3.refPoint.x, charAtPt3.refPoint.y, - charAtPt3.mReply.mOffset, charAtPt3.mReply.mRect.x, - charAtPt3.mReply.mRect.y, charAtPt3.mReply.mRect.width, - charAtPt3.mReply.mRect.height); - if (charAtPt3.mReply.mOffset != leftSideOffset[i]) { - fail(" TestContentEvents: get left side char at point (wrong offset)"); - result = PR_FALSE; - } - - nsQueryContentEvent charAtPt4(PR_TRUE, NS_QUERY_CHARACTER_AT_POINT, widget); - charAtPt4.refPoint.x = charRect.XMost() + 1; - charAtPt4.refPoint.y = charRect.YMost() - 2; - nsr = widget->DispatchEvent(&charAtPt4, eventStatus); - if (NS_FAILED(nsr) || !charAtPt4.mSucceeded) { - fail(" TestContentEvents: get char at point4"); - return PR_FALSE; - } - printf(" NS_QUERY_CHARACTER_AT_POINT: pt={ %ld, %ld }, offset=%lu, rect={ %ld, %ld, %ld, %ld }\n", - charAtPt4.refPoint.x, charAtPt4.refPoint.y, - charAtPt4.mReply.mOffset, charAtPt4.mReply.mRect.x, - charAtPt4.mReply.mRect.y, charAtPt4.mReply.mRect.width, - charAtPt4.mReply.mRect.height); - if (charAtPt4.mReply.mOffset != rightSideOffset[i]) { - fail(" TestContentEvents: get right side char at point4 (wrong offset)"); - result = PR_FALSE; - } - } - return result; -} - PRBool TestApp::TestEditMessages(void) { diff --git a/widget/tests/test_composition_text_querycontent.xul b/widget/tests/test_composition_text_querycontent.xul new file mode 100644 index 00000000000..5f6aaec2ae1 --- /dev/null +++ b/widget/tests/test_composition_text_querycontent.xul @@ -0,0 +1,30 @@ + + + + + + + diff --git a/widget/tests/window_composition_text_querycontent.xul b/widget/tests/window_composition_text_querycontent.xul new file mode 100644 index 00000000000..20378fcb042 --- /dev/null +++ b/widget/tests/window_composition_text_querycontent.xul @@ -0,0 +1,986 @@ + + + + + + + + +