/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 sw=2 et tw=80: */ /* ***** 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) 2008 * 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 "nsQueryContentEventHandler.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 "nsICaret.h" #include "nsFrameSelection.h" #include "nsIFrame.h" #include "nsIView.h" #include "nsIContentIterator.h" #include "nsTextFragment.h" #include "nsTextFrame.h" nsresult NS_NewContentIterator(nsIContentIterator** aInstancePtrResult); /******************************************************************/ /* nsQueryContentEventHandler */ /******************************************************************/ nsQueryContentEventHandler::nsQueryContentEventHandler( nsPresContext* aPresContext) : mPresContext(aPresContext), mPresShell(aPresContext->GetPresShell()), mSelection(nsnull), mFirstSelectedRange(nsnull), mRootContent(nsnull) { } nsresult nsQueryContentEventHandler::Init(nsQueryContentEvent* aEvent) { NS_ASSERTION(aEvent, "aEvent must not be null"); if (mSelection) return NS_OK; aEvent->mSucceeded = PR_FALSE; if (!mPresShell) return NS_ERROR_NOT_AVAILABLE; nsresult rv = mPresShell->GetSelectionForCopy(getter_AddRefs(mSelection)); NS_ENSURE_SUCCESS(rv, rv); NS_ASSERTION(mSelection, "GetSelectionForCopy succeeded, but the result is null"); nsCOMPtr firstRange; rv = mSelection->GetRangeAt(0, getter_AddRefs(firstRange)); // This shell doesn't support selection. if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE; mFirstSelectedRange = do_QueryInterface(firstRange); NS_ENSURE_TRUE(mFirstSelectedRange, NS_ERROR_FAILURE); nsINode* startNode = mFirstSelectedRange->GetStartParent(); NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); mRootContent = startNode->GetSelectionRootContent(mPresShell); NS_ENSURE_TRUE(mRootContent, NS_ERROR_FAILURE); aEvent->mReply.mContentsRoot = mRootContent.get(); return NS_OK; } 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 } static void ConvertToXPNewlines(nsAFlatString& aString) { #if defined(XP_MACOSX) aString.ReplaceSubstring(NS_LITERAL_STRING("\r"), NS_LITERAL_STRING("\n")); #elif defined(XP_WIN) aString.ReplaceSubstring(NS_LITERAL_STRING("\r\n"), NS_LITERAL_STRING("\n")); #endif } static void AppendString(nsAString& aString, nsIContent* aContent) { NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT), "aContent is not a text node!"); const nsTextFragment* text = aContent->GetText(); if (!text) return; text->AppendTo(aString); } static void AppendSubString(nsAString& aString, nsIContent* aContent, PRUint32 aXPOffset, PRUint32 aXPLength) { NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT), "aContent is not a text node!"); const nsTextFragment* text = aContent->GetText(); if (!text) return; 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) str.Assign(PRUnichar('\n')); ConvertToNativeNewlines(str); return str.Length(); } static PRUint32 ConvertToXPOffset(nsIContent* aContent, PRUint32 aNativeOffset) { nsAutoString str; AppendString(str, aContent); 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) { nsCOMPtr 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 domRange(do_QueryInterface(aRange)); NS_ASSERTION(domRange, "aRange doesn't have nsIDOMRange!"); iter->Init(domRange); NS_ASSERTION(aString.IsEmpty(), "aString must be empty string"); nsINode* startNode = aRange->GetStartParent(); nsINode* endNode = aRange->GetEndParent(); if (startNode == endNode && startNode->IsNodeOfType(nsINode::eTEXT)) { nsIContent* content = static_cast(startNode); AppendSubString(aString, content, aRange->StartOffset(), aRange->EndOffset() - aRange->StartOffset()); ConvertToNativeNewlines(aString); return NS_OK; } nsAutoString tmpStr; for (; !iter->IsDone(); iter->Next()) { nsIContent* content = iter->GetCurrentNode(); if (!content) continue; 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) aString.Append(PRUnichar('\n')); } ConvertToNativeNewlines(aString); return NS_OK; } nsresult nsQueryContentEventHandler::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; nsCOMPtr 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... const nsTextFragment* text = aContent->GetText(); NS_ENSURE_TRUE(text, NS_ERROR_FAILURE); if (NS_IS_LOW_SURROGATE(text->CharAt(*aXPOffset)) && NS_IS_HIGH_SURROGATE(text->CharAt(*aXPOffset - 1))) *aXPOffset += aForward ? 1 : -1; return NS_OK; } 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(frame); PRInt32 newOffsetInFrame = offsetInFrame; newOffsetInFrame += aForward ? -1 : 1; textFrame->PeekOffsetCharacter(aForward, &newOffsetInFrame); *aXPOffset = startOffset + newOffsetInFrame; return NS_OK; } nsresult nsQueryContentEventHandler::SetRangeFromFlatTextOffset( nsIRange* aRange, PRUint32 aNativeOffset, PRUint32 aNativeLength, PRBool aExpandToClusterBoundaries) { nsCOMPtr iter; nsresult rv = NS_NewContentIterator(getter_AddRefs(iter)); NS_ENSURE_SUCCESS(rv, rv); NS_ASSERTION(iter, "NS_NewContentIterator succeeded, but the result is null"); rv = iter->Init(mRootContent); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr domRange(do_QueryInterface(aRange)); NS_ASSERTION(domRange, "aRange doesn't have nsIDOMRange!"); PRUint32 nativeOffset = 0; PRUint32 nativeEndOffset = aNativeOffset + aNativeLength; nsIContent* content = nsnull; for (; !iter->IsDone(); iter->Next()) { content = iter->GetCurrentNode(); if (!content) continue; PRUint32 nativeTextLength; nativeTextLength = GetNativeTextLength(content); if (nativeTextLength == 0) continue; if (nativeOffset <= aNativeOffset && aNativeOffset < nativeOffset + nativeTextLength) { nsCOMPtr domNode(do_QueryInterface(content)); NS_ASSERTION(domNode, "aContent doesn't have nsIDOMNode!"); PRUint32 xpOffset = content->IsNodeOfType(nsINode::eTEXT) ? ConvertToXPOffset(content, aNativeOffset - nativeOffset) : 0; if (aExpandToClusterBoundaries) { rv = ExpandToClusterBoundary(content, PR_FALSE, &xpOffset); NS_ENSURE_SUCCESS(rv, rv); } rv = domRange->SetStart(domNode, PRInt32(xpOffset)); NS_ENSURE_SUCCESS(rv, rv); if (aNativeLength == 0) { // Ensure that the end offset and the start offset are same. rv = domRange->SetEnd(domNode, PRInt32(xpOffset)); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } } if (nativeEndOffset <= nativeOffset + nativeTextLength) { nsCOMPtr domNode(do_QueryInterface(content)); NS_ASSERTION(domNode, "aContent doesn't have nsIDOMNode!"); PRUint32 xpOffset; if (content->IsNodeOfType(nsINode::eTEXT)) { xpOffset = ConvertToXPOffset(content, nativeEndOffset - nativeOffset); if (aExpandToClusterBoundaries) { rv = ExpandToClusterBoundary(content, PR_TRUE, &xpOffset); NS_ENSURE_SUCCESS(rv, rv); } } else { // Use first position of next node, because the end node is ignored // by ContentIterator when the offset is zero. xpOffset = 0; iter->Next(); if (iter->IsDone()) break; domNode = do_QueryInterface(iter->GetCurrentNode()); } rv = domRange->SetEnd(domNode, PRInt32(xpOffset)); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nativeOffset += nativeTextLength; } if (nativeOffset < aNativeOffset) return NS_ERROR_FAILURE; nsCOMPtr domNode(do_QueryInterface(mRootContent)); NS_ASSERTION(domNode, "lastContent doesn't have nsIDOMNode!"); if (!content) { rv = domRange->SetStart(domNode, 0); 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) { 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); NS_ENSURE_SUCCESS(rv, rv); if (!isCollapsed) { nsCOMPtr domRange; rv = mSelection->GetRangeAt(0, getter_AddRefs(domRange)); NS_ENSURE_SUCCESS(rv, rv); NS_ASSERTION(domRange, "GetRangeAt succeeded, but the result is null"); nsCOMPtr range(do_QueryInterface(domRange)); NS_ENSURE_TRUE(range, NS_ERROR_FAILURE); rv = GenerateFlatTextContent(range, aEvent->mReply.mString); NS_ENSURE_SUCCESS(rv, rv); } aEvent->mSucceeded = PR_TRUE; return NS_OK; } nsresult nsQueryContentEventHandler::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"); nsCOMPtr range = new nsRange(); NS_ENSURE_TRUE(range, NS_ERROR_OUT_OF_MEMORY); rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset, aEvent->mInput.mLength, PR_FALSE); NS_ENSURE_SUCCESS(rv, rv); rv = GenerateFlatTextContent(range, aEvent->mReply.mString); NS_ENSURE_SUCCESS(rv, rv); aEvent->mSucceeded = PR_TRUE; return NS_OK; } nsresult nsQueryContentEventHandler::QueryRectFor(nsQueryContentEvent* aEvent, nsIRange* aRange, nsICaret* aCaret) { PRInt32 offsetInFrame; nsIFrame* frame; nsresult rv = GetStartFrameAndOffset(aRange, &frame, &offsetInFrame); NS_ENSURE_SUCCESS(rv, rv); nsPoint posInFrame; rv = frame->GetPointFromOffset(aRange->StartOffset(), &posInFrame); NS_ENSURE_SUCCESS(rv, rv); aEvent->mReply.mRect.y = posInFrame.y; aEvent->mReply.mRect.height = frame->GetSize().height; if (aEvent->message == NS_QUERY_CHARACTER_RECT) { nsPoint nextPos; rv = frame->GetPointFromOffset(aRange->EndOffset(), &nextPos); NS_ENSURE_SUCCESS(rv, rv); aEvent->mReply.mRect.x = PR_MIN(posInFrame.x, nextPos.x); aEvent->mReply.mRect.width = PR_ABS(posInFrame.x - nextPos.x); } else { aEvent->mReply.mRect.x = posInFrame.x; aEvent->mReply.mRect.width = aCaret->GetCaretRect().width; } // The coordinates are app units here, they will be converted to system // coordinates by view manager. rv = ConvertToRootViewRelativeOffset(frame, aEvent->mReply.mRect); NS_ENSURE_SUCCESS(rv, rv); aEvent->mSucceeded = PR_TRUE; return NS_OK; } nsresult nsQueryContentEventHandler::OnQueryCharacterRect(nsQueryContentEvent* aEvent) { nsresult rv = Init(aEvent); if (NS_FAILED(rv)) return rv; nsCOMPtr range = new nsRange(); NS_ENSURE_TRUE(range, NS_ERROR_OUT_OF_MEMORY); rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset, 1, PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); if (range->Collapsed()) { // There is no character at the offset. return NS_OK; } return QueryRectFor(aEvent, range, nsnull); } nsresult nsQueryContentEventHandler::OnQueryCaretRect(nsQueryContentEvent* aEvent) { nsresult rv = Init(aEvent); if (NS_FAILED(rv)) return rv; nsCOMPtr caret; rv = mPresShell->GetCaret(getter_AddRefs(caret)); NS_ENSURE_SUCCESS(rv, rv); NS_ASSERTION(caret, "GetCaret succeeded, but the result is null"); // 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); NS_ENSURE_SUCCESS(rv, rv); if (offset == aEvent->mInput.mOffset) { PRBool isCollapsed; rv = caret->GetCaretCoordinates(nsICaret::eTopLevelWindowCoordinates, mSelection, &aEvent->mReply.mRect, &isCollapsed, nsnull); NS_ENSURE_SUCCESS(rv, rv); aEvent->mSucceeded = PR_TRUE; return NS_OK; } } // Otherwise, we should set the guessed caret rect. nsCOMPtr 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); } nsresult nsQueryContentEventHandler::GetFlatTextOffsetOfRange(nsIRange* aRange, PRUint32* aNativeOffset) { NS_ASSERTION(aNativeOffset, "param is invalid"); nsCOMPtr prev = new nsRange(); NS_ENSURE_TRUE(prev, NS_ERROR_OUT_OF_MEMORY); nsCOMPtr domPrev(do_QueryInterface(prev)); NS_ASSERTION(domPrev, "nsRange doesn't have nsIDOMRange??"); nsCOMPtr rootDOMNode(do_QueryInterface(mRootContent)); domPrev->SetStart(rootDOMNode, 0); nsINode* startNode = aRange->GetStartParent(); NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); PRInt32 startOffset = aRange->StartOffset(); nsCOMPtr startDOMNode(do_QueryInterface(startNode)); NS_ASSERTION(startDOMNode, "startNode doesn't have nsIDOMNode"); domPrev->SetEnd(startDOMNode, startOffset); 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) { NS_ASSERTION(aRange && aFrame && aOffsetInFrame, "params are invalid"); nsIContent* content = nsnull; nsINode* node = aRange->GetStartParent(); if (node && node->IsNodeOfType(nsINode::eCONTENT)) content = static_cast(node); NS_ASSERTION(content, "the start node doesn't have nsIContent!"); nsCOMPtr fs = mPresShell->FrameSelection(); *aFrame = fs->GetFrameForNodeOffset(content, aRange->StartOffset(), 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) { 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; }