michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ContentEventHandler.h" michael@0: #include "mozilla/IMEStateManager.h" michael@0: #include "mozilla/TextEvents.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "nsCaret.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCopySupport.h" michael@0: #include "nsFocusManager.h" michael@0: #include "nsFrameSelection.h" michael@0: #include "nsIContentIterator.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsISelection.h" michael@0: #include "nsISelectionController.h" michael@0: #include "nsISelectionPrivate.h" michael@0: #include "nsIDOMRange.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsIObjectFrame.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsRange.h" michael@0: #include "nsTextFragment.h" michael@0: #include "nsTextFrame.h" michael@0: #include "nsView.h" michael@0: michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: michael@0: using namespace dom; michael@0: using namespace widget; michael@0: michael@0: /******************************************************************/ michael@0: /* ContentEventHandler */ michael@0: /******************************************************************/ michael@0: michael@0: ContentEventHandler::ContentEventHandler(nsPresContext* aPresContext) michael@0: : mPresContext(aPresContext) michael@0: , mPresShell(aPresContext->GetPresShell()) michael@0: , mSelection(nullptr) michael@0: , mFirstSelectedRange(nullptr) michael@0: , mRootContent(nullptr) michael@0: { michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::InitBasic() michael@0: { michael@0: NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: // If text frame which has overflowing selection underline is dirty, michael@0: // we need to flush the pending reflow here. michael@0: mPresShell->FlushPendingNotifications(Flush_Layout); michael@0: michael@0: // Flushing notifications can cause mPresShell to be destroyed (bug 577963). michael@0: NS_ENSURE_TRUE(!mPresShell->IsDestroying(), NS_ERROR_FAILURE); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::InitCommon() michael@0: { michael@0: if (mSelection) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv = InitBasic(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCopySupport::GetSelectionForCopy(mPresShell->GetDocument(), michael@0: getter_AddRefs(mSelection)); michael@0: michael@0: nsCOMPtr firstRange; michael@0: rv = mSelection->GetRangeAt(0, getter_AddRefs(firstRange)); michael@0: // This shell doesn't support selection. michael@0: if (NS_FAILED(rv)) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: mFirstSelectedRange = static_cast(firstRange.get()); michael@0: michael@0: nsINode* startNode = mFirstSelectedRange->GetStartParent(); michael@0: NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); michael@0: nsINode* endNode = mFirstSelectedRange->GetEndParent(); michael@0: NS_ENSURE_TRUE(endNode, NS_ERROR_FAILURE); michael@0: michael@0: // See bug 537041 comment 5, the range could have removed node. michael@0: NS_ENSURE_TRUE(startNode->GetCurrentDoc() == mPresShell->GetDocument(), michael@0: NS_ERROR_NOT_AVAILABLE); michael@0: NS_ASSERTION(startNode->GetCurrentDoc() == endNode->GetCurrentDoc(), michael@0: "mFirstSelectedRange crosses the document boundary"); michael@0: michael@0: mRootContent = startNode->GetSelectionRootContent(mPresShell); michael@0: NS_ENSURE_TRUE(mRootContent, NS_ERROR_FAILURE); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::Init(WidgetQueryContentEvent* aEvent) michael@0: { michael@0: NS_ASSERTION(aEvent, "aEvent must not be null"); michael@0: michael@0: nsresult rv = InitCommon(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: aEvent->mSucceeded = false; michael@0: michael@0: aEvent->mReply.mContentsRoot = mRootContent.get(); michael@0: michael@0: bool isCollapsed; michael@0: rv = mSelection->GetIsCollapsed(&isCollapsed); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_AVAILABLE); michael@0: aEvent->mReply.mHasSelection = !isCollapsed; michael@0: michael@0: nsRefPtr caret = mPresShell->GetCaret(); michael@0: NS_ASSERTION(caret, "GetCaret returned null"); michael@0: michael@0: nsRect r; michael@0: nsIFrame* frame = caret->GetGeometry(mSelection, &r); michael@0: NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); michael@0: michael@0: aEvent->mReply.mFocusedWidget = frame->GetNearestWidget(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::Init(WidgetSelectionEvent* aEvent) michael@0: { michael@0: NS_ASSERTION(aEvent, "aEvent must not be null"); michael@0: michael@0: nsresult rv = InitCommon(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: aEvent->mSucceeded = false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIContent* michael@0: ContentEventHandler::GetFocusedContent() michael@0: { michael@0: nsIDocument* doc = mPresShell->GetDocument(); michael@0: if (!doc) { michael@0: return nullptr; michael@0: } michael@0: nsCOMPtr window = do_QueryInterface(doc->GetWindow()); michael@0: nsCOMPtr focusedWindow; michael@0: return nsFocusManager::GetFocusedDescendant(window, true, michael@0: getter_AddRefs(focusedWindow)); michael@0: } michael@0: michael@0: bool michael@0: ContentEventHandler::IsPlugin(nsIContent* aContent) michael@0: { michael@0: return aContent && michael@0: aContent->GetDesiredIMEState().mEnabled == IMEState::PLUGIN; michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::QueryContentRect(nsIContent* aContent, michael@0: WidgetQueryContentEvent* aEvent) michael@0: { michael@0: NS_PRECONDITION(aContent, "aContent must not be null"); michael@0: michael@0: nsIFrame* frame = aContent->GetPrimaryFrame(); michael@0: NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); michael@0: michael@0: // get rect for first frame michael@0: nsRect resultRect(nsPoint(0, 0), frame->GetRect().Size()); michael@0: nsresult rv = ConvertToRootViewRelativeOffset(frame, resultRect); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // account for any additional frames michael@0: while ((frame = frame->GetNextContinuation()) != nullptr) { michael@0: nsRect frameRect(nsPoint(0, 0), frame->GetRect().Size()); michael@0: rv = ConvertToRootViewRelativeOffset(frame, frameRect); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: resultRect.UnionRect(resultRect, frameRect); michael@0: } michael@0: michael@0: aEvent->mReply.mRect = michael@0: resultRect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel()); michael@0: aEvent->mSucceeded = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Editor places a bogus BR node under its root content if the editor doesn't michael@0: // have any text. This happens even for single line editors. michael@0: // When we get text content and when we change the selection, michael@0: // we don't want to include the bogus BRs at the end. michael@0: static bool IsContentBR(nsIContent* aContent) michael@0: { michael@0: return aContent->IsHTML() && michael@0: aContent->Tag() == nsGkAtoms::br && michael@0: !aContent->AttrValueIs(kNameSpaceID_None, michael@0: nsGkAtoms::type, michael@0: nsGkAtoms::moz, michael@0: eIgnoreCase) && michael@0: !aContent->AttrValueIs(kNameSpaceID_None, michael@0: nsGkAtoms::mozeditorbogusnode, michael@0: nsGkAtoms::_true, michael@0: eIgnoreCase); michael@0: } michael@0: michael@0: static void ConvertToNativeNewlines(nsAFlatString& aString) michael@0: { michael@0: #if defined(XP_MACOSX) michael@0: // XXX Mac OS X doesn't use "\r". michael@0: aString.ReplaceSubstring(NS_LITERAL_STRING("\n"), NS_LITERAL_STRING("\r")); michael@0: #elif defined(XP_WIN) michael@0: aString.ReplaceSubstring(NS_LITERAL_STRING("\n"), NS_LITERAL_STRING("\r\n")); michael@0: #endif michael@0: } michael@0: michael@0: static void AppendString(nsAString& aString, nsIContent* aContent) michael@0: { michael@0: NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT), michael@0: "aContent is not a text node!"); michael@0: const nsTextFragment* text = aContent->GetText(); michael@0: if (!text) { michael@0: return; michael@0: } michael@0: text->AppendTo(aString); michael@0: } michael@0: michael@0: static void AppendSubString(nsAString& aString, nsIContent* aContent, michael@0: uint32_t aXPOffset, uint32_t aXPLength) michael@0: { michael@0: NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT), michael@0: "aContent is not a text node!"); michael@0: const nsTextFragment* text = aContent->GetText(); michael@0: if (!text) { michael@0: return; michael@0: } michael@0: text->AppendTo(aString, int32_t(aXPOffset), int32_t(aXPLength)); michael@0: } michael@0: michael@0: #if defined(XP_WIN) michael@0: static uint32_t CountNewlinesInXPLength(nsIContent* aContent, michael@0: uint32_t aXPLength) michael@0: { michael@0: NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT), michael@0: "aContent is not a text node!"); michael@0: const nsTextFragment* text = aContent->GetText(); michael@0: if (!text) { michael@0: return 0; michael@0: } michael@0: // For automated tests, we should abort on debug build. michael@0: NS_ABORT_IF_FALSE( michael@0: (aXPLength == UINT32_MAX || aXPLength <= text->GetLength()), michael@0: "aXPLength is out-of-bounds"); michael@0: const uint32_t length = std::min(aXPLength, text->GetLength()); michael@0: uint32_t newlines = 0; michael@0: for (uint32_t i = 0; i < length; ++i) { michael@0: if (text->CharAt(i) == '\n') { michael@0: ++newlines; michael@0: } michael@0: } michael@0: return newlines; michael@0: } michael@0: michael@0: static uint32_t CountNewlinesInNativeLength(nsIContent* aContent, michael@0: uint32_t aNativeLength) michael@0: { michael@0: NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT), michael@0: "aContent is not a text node!"); michael@0: const nsTextFragment* text = aContent->GetText(); michael@0: if (!text) { michael@0: return 0; michael@0: } michael@0: // For automated tests, we should abort on debug build. michael@0: MOZ_ASSERT( michael@0: (aNativeLength == UINT32_MAX || aNativeLength <= text->GetLength() * 2), michael@0: "aNativeLength is unexpected value"); michael@0: const uint32_t xpLength = text->GetLength(); michael@0: uint32_t newlines = 0; michael@0: for (uint32_t i = 0, nativeOffset = 0; michael@0: i < xpLength && nativeOffset < aNativeLength; michael@0: ++i, ++nativeOffset) { michael@0: // For automated tests, we should abort on debug build. michael@0: NS_ABORT_IF_FALSE(i < text->GetLength(), "i is out-of-bounds"); michael@0: if (text->CharAt(i) == '\n') { michael@0: ++newlines; michael@0: ++nativeOffset; michael@0: } michael@0: } michael@0: return newlines; michael@0: } michael@0: #endif michael@0: michael@0: /* static */ uint32_t michael@0: ContentEventHandler::GetNativeTextLength(nsIContent* aContent, michael@0: uint32_t aMaxLength) michael@0: { michael@0: return GetTextLength(aContent, LINE_BREAK_TYPE_NATIVE, aMaxLength); michael@0: } michael@0: michael@0: /* static */ uint32_t michael@0: ContentEventHandler::GetTextLength(nsIContent* aContent, michael@0: LineBreakType aLineBreakType, michael@0: uint32_t aMaxLength) michael@0: { michael@0: if (aContent->IsNodeOfType(nsINode::eTEXT)) { michael@0: uint32_t textLengthDifference = michael@0: #if defined(XP_MACOSX) michael@0: // On Mac, the length of a native newline ("\r") is equal to the length of michael@0: // the XP newline ("\n"), so the native length is the same as the XP michael@0: // length. michael@0: 0; michael@0: #elif defined(XP_WIN) michael@0: // On Windows, the length of a native newline ("\r\n") is twice the length michael@0: // of the XP newline ("\n"), so XP length is equal to the length of the michael@0: // native offset plus the number of newlines encountered in the string. michael@0: (aLineBreakType == LINE_BREAK_TYPE_NATIVE) ? michael@0: CountNewlinesInXPLength(aContent, aMaxLength) : 0; michael@0: #else michael@0: // On other platforms, the native and XP newlines are the same. michael@0: 0; michael@0: #endif michael@0: michael@0: const nsTextFragment* text = aContent->GetText(); michael@0: if (!text) { michael@0: return 0; michael@0: } michael@0: uint32_t length = std::min(text->GetLength(), aMaxLength); michael@0: return length + textLengthDifference; michael@0: } else if (IsContentBR(aContent)) { michael@0: #if defined(XP_WIN) michael@0: // Length of \r\n michael@0: return (aLineBreakType == LINE_BREAK_TYPE_NATIVE) ? 2 : 1; michael@0: #else michael@0: return 1; michael@0: #endif michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: static uint32_t ConvertToXPOffset(nsIContent* aContent, uint32_t aNativeOffset) michael@0: { michael@0: #if defined(XP_MACOSX) michael@0: // On Mac, the length of a native newline ("\r") is equal to the length of michael@0: // the XP newline ("\n"), so the native offset is the same as the XP offset. michael@0: return aNativeOffset; michael@0: #elif defined(XP_WIN) michael@0: // On Windows, the length of a native newline ("\r\n") is twice the length of michael@0: // the XP newline ("\n"), so XP offset is equal to the length of the native michael@0: // offset minus the number of newlines encountered in the string. michael@0: return aNativeOffset - CountNewlinesInNativeLength(aContent, aNativeOffset); michael@0: #else michael@0: // On other platforms, the native and XP newlines are the same. michael@0: return aNativeOffset; michael@0: #endif michael@0: } michael@0: michael@0: static nsresult GenerateFlatTextContent(nsRange* aRange, michael@0: nsAFlatString& aString, michael@0: LineBreakType aLineBreakType) michael@0: { michael@0: nsCOMPtr iter = NS_NewContentIterator(); michael@0: iter->Init(aRange); michael@0: michael@0: NS_ASSERTION(aString.IsEmpty(), "aString must be empty string"); michael@0: michael@0: nsINode* startNode = aRange->GetStartParent(); michael@0: NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); michael@0: nsINode* endNode = aRange->GetEndParent(); michael@0: NS_ENSURE_TRUE(endNode, NS_ERROR_FAILURE); michael@0: michael@0: if (startNode == endNode && startNode->IsNodeOfType(nsINode::eTEXT)) { michael@0: nsIContent* content = static_cast(startNode); michael@0: AppendSubString(aString, content, aRange->StartOffset(), michael@0: aRange->EndOffset() - aRange->StartOffset()); michael@0: ConvertToNativeNewlines(aString); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoString tmpStr; michael@0: for (; !iter->IsDone(); iter->Next()) { michael@0: nsINode* node = iter->GetCurrentNode(); michael@0: if (!node) { michael@0: break; michael@0: } michael@0: if (!node->IsNodeOfType(nsINode::eCONTENT)) { michael@0: continue; michael@0: } michael@0: nsIContent* content = static_cast(node); michael@0: michael@0: if (content->IsNodeOfType(nsINode::eTEXT)) { michael@0: if (content == startNode) { michael@0: AppendSubString(aString, content, aRange->StartOffset(), michael@0: content->TextLength() - aRange->StartOffset()); michael@0: } else if (content == endNode) { michael@0: AppendSubString(aString, content, 0, aRange->EndOffset()); michael@0: } else { michael@0: AppendString(aString, content); michael@0: } michael@0: } else if (IsContentBR(content)) { michael@0: aString.Append(char16_t('\n')); michael@0: } michael@0: } michael@0: if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) { michael@0: ConvertToNativeNewlines(aString); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::ExpandToClusterBoundary(nsIContent* aContent, michael@0: bool aForward, michael@0: uint32_t* aXPOffset) michael@0: { michael@0: // XXX This method assumes that the frame boundaries must be cluster michael@0: // boundaries. It's false, but no problem now, maybe. michael@0: if (!aContent->IsNodeOfType(nsINode::eTEXT) || michael@0: *aXPOffset == 0 || *aXPOffset == aContent->TextLength()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ASSERTION(*aXPOffset <= aContent->TextLength(), michael@0: "offset is out of range."); michael@0: michael@0: nsRefPtr fs = mPresShell->FrameSelection(); michael@0: int32_t offsetInFrame; michael@0: nsFrameSelection::HINT hint = michael@0: aForward ? nsFrameSelection::HINTLEFT : nsFrameSelection::HINTRIGHT; michael@0: nsIFrame* frame = fs->GetFrameForNodeOffset(aContent, int32_t(*aXPOffset), michael@0: hint, &offsetInFrame); michael@0: if (!frame) { michael@0: // This content doesn't have any frames, we only can check surrogate pair... michael@0: const nsTextFragment* text = aContent->GetText(); michael@0: NS_ENSURE_TRUE(text, NS_ERROR_FAILURE); michael@0: if (NS_IS_LOW_SURROGATE(text->CharAt(*aXPOffset)) && michael@0: NS_IS_HIGH_SURROGATE(text->CharAt(*aXPOffset - 1))) { michael@0: *aXPOffset += aForward ? 1 : -1; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: int32_t startOffset, endOffset; michael@0: nsresult rv = frame->GetOffsets(startOffset, endOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (*aXPOffset == static_cast(startOffset) || michael@0: *aXPOffset == static_cast(endOffset)) { michael@0: return NS_OK; michael@0: } michael@0: if (frame->GetType() != nsGkAtoms::textFrame) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: nsTextFrame* textFrame = static_cast(frame); michael@0: int32_t newOffsetInFrame = *aXPOffset - startOffset; michael@0: newOffsetInFrame += aForward ? -1 : 1; michael@0: textFrame->PeekOffsetCharacter(aForward, &newOffsetInFrame); michael@0: *aXPOffset = startOffset + newOffsetInFrame; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::SetRangeFromFlatTextOffset(nsRange* aRange, michael@0: uint32_t aOffset, michael@0: uint32_t aLength, michael@0: LineBreakType aLineBreakType, michael@0: bool aExpandToClusterBoundaries, michael@0: uint32_t* aNewOffset) michael@0: { michael@0: if (aNewOffset) { michael@0: *aNewOffset = aOffset; michael@0: } michael@0: michael@0: nsCOMPtr iter = NS_NewPreContentIterator(); michael@0: nsresult rv = iter->Init(mRootContent); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t offset = 0; michael@0: uint32_t endOffset = aOffset + aLength; michael@0: bool startSet = false; michael@0: for (; !iter->IsDone(); iter->Next()) { michael@0: nsINode* node = iter->GetCurrentNode(); michael@0: if (!node) { michael@0: break; michael@0: } michael@0: if (!node->IsNodeOfType(nsINode::eCONTENT)) { michael@0: continue; michael@0: } michael@0: nsIContent* content = static_cast(node); michael@0: michael@0: uint32_t textLength = GetTextLength(content, aLineBreakType); michael@0: if (!textLength) { michael@0: continue; michael@0: } michael@0: michael@0: if (offset <= aOffset && aOffset < offset + textLength) { michael@0: nsCOMPtr domNode(do_QueryInterface(content)); michael@0: NS_ASSERTION(domNode, "aContent doesn't have nsIDOMNode!"); michael@0: michael@0: uint32_t xpOffset; michael@0: if (!content->IsNodeOfType(nsINode::eTEXT)) { michael@0: xpOffset = 0; michael@0: } else { michael@0: xpOffset = aOffset - offset; michael@0: if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) { michael@0: xpOffset = ConvertToXPOffset(content, xpOffset); michael@0: } michael@0: } michael@0: michael@0: if (aExpandToClusterBoundaries) { michael@0: uint32_t oldXPOffset = xpOffset; michael@0: rv = ExpandToClusterBoundary(content, false, &xpOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (aNewOffset) { michael@0: // This is correct since a cluster shouldn't include line break. michael@0: *aNewOffset -= (oldXPOffset - xpOffset); michael@0: } michael@0: } michael@0: michael@0: rv = aRange->SetStart(domNode, int32_t(xpOffset)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: startSet = true; michael@0: if (aLength == 0) { michael@0: // Ensure that the end offset and the start offset are same. michael@0: rv = aRange->SetEnd(domNode, int32_t(xpOffset)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: if (endOffset <= offset + textLength) { michael@0: nsCOMPtr domNode(do_QueryInterface(content)); michael@0: NS_ASSERTION(domNode, "aContent doesn't have nsIDOMNode!"); michael@0: michael@0: uint32_t xpOffset; michael@0: if (content->IsNodeOfType(nsINode::eTEXT)) { michael@0: xpOffset = endOffset - offset; michael@0: if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) { michael@0: xpOffset = ConvertToXPOffset(content, xpOffset); michael@0: } michael@0: if (aExpandToClusterBoundaries) { michael@0: rv = ExpandToClusterBoundary(content, true, &xpOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } else { michael@0: // Use first position of next node, because the end node is ignored michael@0: // by ContentIterator when the offset is zero. michael@0: xpOffset = 0; michael@0: iter->Next(); michael@0: if (iter->IsDone()) { michael@0: break; michael@0: } michael@0: domNode = do_QueryInterface(iter->GetCurrentNode()); michael@0: } michael@0: michael@0: rv = aRange->SetEnd(domNode, int32_t(xpOffset)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return NS_OK; michael@0: } michael@0: michael@0: offset += textLength; michael@0: } michael@0: michael@0: if (offset < aOffset) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCOMPtr domNode(do_QueryInterface(mRootContent)); michael@0: NS_ASSERTION(domNode, "lastContent doesn't have nsIDOMNode!"); michael@0: if (!startSet) { michael@0: MOZ_ASSERT(!mRootContent->IsNodeOfType(nsINode::eTEXT)); michael@0: rv = aRange->SetStart(domNode, int32_t(mRootContent->GetChildCount())); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (aNewOffset) { michael@0: *aNewOffset = offset; michael@0: } michael@0: } michael@0: rv = aRange->SetEnd(domNode, int32_t(mRootContent->GetChildCount())); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "nsIDOMRange::SetEnd failed"); michael@0: return rv; michael@0: } michael@0: michael@0: /* static */ LineBreakType michael@0: ContentEventHandler::GetLineBreakType(WidgetQueryContentEvent* aEvent) michael@0: { michael@0: return GetLineBreakType(aEvent->mUseNativeLineBreak); michael@0: } michael@0: michael@0: /* static */ LineBreakType michael@0: ContentEventHandler::GetLineBreakType(WidgetSelectionEvent* aEvent) michael@0: { michael@0: return GetLineBreakType(aEvent->mUseNativeLineBreak); michael@0: } michael@0: michael@0: /* static */ LineBreakType michael@0: ContentEventHandler::GetLineBreakType(bool aUseNativeLineBreak) michael@0: { michael@0: return aUseNativeLineBreak ? michael@0: LINE_BREAK_TYPE_NATIVE : LINE_BREAK_TYPE_XP; michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::OnQuerySelectedText(WidgetQueryContentEvent* aEvent) michael@0: { michael@0: nsresult rv = Init(aEvent); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: NS_ASSERTION(aEvent->mReply.mString.IsEmpty(), michael@0: "The reply string must be empty"); michael@0: michael@0: LineBreakType lineBreakType = GetLineBreakType(aEvent); michael@0: rv = GetFlatTextOffsetOfRange(mRootContent, mFirstSelectedRange, michael@0: &aEvent->mReply.mOffset, lineBreakType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr anchorDomNode, focusDomNode; michael@0: rv = mSelection->GetAnchorNode(getter_AddRefs(anchorDomNode)); michael@0: NS_ENSURE_TRUE(anchorDomNode, NS_ERROR_FAILURE); michael@0: rv = mSelection->GetFocusNode(getter_AddRefs(focusDomNode)); michael@0: NS_ENSURE_TRUE(focusDomNode, NS_ERROR_FAILURE); michael@0: michael@0: int32_t anchorOffset, focusOffset; michael@0: rv = mSelection->GetAnchorOffset(&anchorOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mSelection->GetFocusOffset(&focusOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr anchorNode(do_QueryInterface(anchorDomNode)); michael@0: nsCOMPtr focusNode(do_QueryInterface(focusDomNode)); michael@0: NS_ENSURE_TRUE(anchorNode && focusNode, NS_ERROR_UNEXPECTED); michael@0: michael@0: int16_t compare = nsContentUtils::ComparePoints(anchorNode, anchorOffset, michael@0: focusNode, focusOffset); michael@0: aEvent->mReply.mReversed = compare > 0; michael@0: michael@0: if (compare) { michael@0: rv = GenerateFlatTextContent(mFirstSelectedRange, aEvent->mReply.mString, michael@0: lineBreakType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: aEvent->mSucceeded = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::OnQueryTextContent(WidgetQueryContentEvent* aEvent) michael@0: { michael@0: nsresult rv = Init(aEvent); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: NS_ASSERTION(aEvent->mReply.mString.IsEmpty(), michael@0: "The reply string must be empty"); michael@0: michael@0: LineBreakType lineBreakType = GetLineBreakType(aEvent); michael@0: michael@0: nsRefPtr range = new nsRange(mRootContent); michael@0: rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset, michael@0: aEvent->mInput.mLength, lineBreakType, false, michael@0: &aEvent->mReply.mOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = GenerateFlatTextContent(range, aEvent->mReply.mString, lineBreakType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: aEvent->mSucceeded = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Adjust to use a child node if possible michael@0: // to make the returned rect more accurate michael@0: static nsINode* AdjustTextRectNode(nsINode* aNode, michael@0: int32_t& aNodeOffset) michael@0: { michael@0: int32_t childCount = int32_t(aNode->GetChildCount()); michael@0: nsINode* node = aNode; michael@0: if (childCount) { michael@0: if (aNodeOffset < childCount) { michael@0: node = aNode->GetChildAt(aNodeOffset); michael@0: aNodeOffset = 0; michael@0: } else if (aNodeOffset == childCount) { michael@0: node = aNode->GetChildAt(childCount - 1); michael@0: aNodeOffset = node->IsNodeOfType(nsINode::eTEXT) ? michael@0: static_cast(static_cast(node)->TextLength()) : 1; michael@0: } michael@0: } michael@0: return node; michael@0: } michael@0: michael@0: // Similar to nsFrameSelection::GetFrameForNodeOffset, michael@0: // but this is more flexible for OnQueryTextRect to use michael@0: static nsresult GetFrameForTextRect(nsINode* aNode, michael@0: int32_t aNodeOffset, michael@0: bool aHint, michael@0: nsIFrame** aReturnFrame) michael@0: { michael@0: NS_ENSURE_TRUE(aNode && aNode->IsNodeOfType(nsINode::eCONTENT), michael@0: NS_ERROR_UNEXPECTED); michael@0: nsIContent* content = static_cast(aNode); michael@0: nsIFrame* frame = content->GetPrimaryFrame(); michael@0: NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); michael@0: int32_t childNodeOffset = 0; michael@0: return frame->GetChildFrameContainingOffset(aNodeOffset, aHint, michael@0: &childNodeOffset, aReturnFrame); michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::OnQueryTextRect(WidgetQueryContentEvent* aEvent) michael@0: { michael@0: nsresult rv = Init(aEvent); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: LineBreakType lineBreakType = GetLineBreakType(aEvent); michael@0: nsRefPtr range = new nsRange(mRootContent); michael@0: rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset, michael@0: aEvent->mInput.mLength, lineBreakType, true, michael@0: &aEvent->mReply.mOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = GenerateFlatTextContent(range, aEvent->mReply.mString, lineBreakType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // used to iterate over all contents and their frames michael@0: nsCOMPtr iter = NS_NewContentIterator(); michael@0: iter->Init(range); michael@0: michael@0: // get the starting frame michael@0: int32_t nodeOffset = range->StartOffset(); michael@0: nsINode* node = iter->GetCurrentNode(); michael@0: if (!node) { michael@0: node = AdjustTextRectNode(range->GetStartParent(), nodeOffset); michael@0: } michael@0: nsIFrame* firstFrame = nullptr; michael@0: rv = GetFrameForTextRect(node, nodeOffset, true, &firstFrame); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // get the starting frame rect michael@0: nsRect rect(nsPoint(0, 0), firstFrame->GetRect().Size()); michael@0: rv = ConvertToRootViewRelativeOffset(firstFrame, rect); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsRect frameRect = rect; michael@0: nsPoint ptOffset; michael@0: firstFrame->GetPointFromOffset(nodeOffset, &ptOffset); michael@0: // minus 1 to avoid creating an empty rect michael@0: rect.x += ptOffset.x - 1; michael@0: rect.width -= ptOffset.x - 1; michael@0: michael@0: // get the ending frame michael@0: nodeOffset = range->EndOffset(); michael@0: node = AdjustTextRectNode(range->GetEndParent(), nodeOffset); michael@0: nsIFrame* lastFrame = nullptr; michael@0: rv = GetFrameForTextRect(node, nodeOffset, range->Collapsed(), &lastFrame); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // iterate over all covered frames michael@0: for (nsIFrame* frame = firstFrame; frame != lastFrame;) { michael@0: frame = frame->GetNextContinuation(); michael@0: if (!frame) { michael@0: do { michael@0: iter->Next(); michael@0: node = iter->GetCurrentNode(); michael@0: if (!node) { michael@0: break; michael@0: } michael@0: if (!node->IsNodeOfType(nsINode::eCONTENT)) { michael@0: continue; michael@0: } michael@0: frame = static_cast(node)->GetPrimaryFrame(); michael@0: } while (!frame && !iter->IsDone()); michael@0: if (!frame) { michael@0: // this can happen when the end offset of the range is 0. michael@0: frame = lastFrame; michael@0: } michael@0: } michael@0: frameRect.SetRect(nsPoint(0, 0), frame->GetRect().Size()); michael@0: rv = ConvertToRootViewRelativeOffset(frame, frameRect); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (frame != lastFrame) { michael@0: // not last frame, so just add rect to previous result michael@0: rect.UnionRect(rect, frameRect); michael@0: } michael@0: } michael@0: michael@0: // get the ending frame rect michael@0: lastFrame->GetPointFromOffset(nodeOffset, &ptOffset); michael@0: // minus 1 to avoid creating an empty rect michael@0: frameRect.width -= lastFrame->GetRect().width - ptOffset.x - 1; michael@0: michael@0: if (firstFrame == lastFrame) { michael@0: rect.IntersectRect(rect, frameRect); michael@0: } else { michael@0: rect.UnionRect(rect, frameRect); michael@0: } michael@0: aEvent->mReply.mRect = michael@0: rect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel()); michael@0: aEvent->mSucceeded = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::OnQueryEditorRect(WidgetQueryContentEvent* aEvent) michael@0: { michael@0: nsresult rv = Init(aEvent); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: nsIContent* focusedContent = GetFocusedContent(); michael@0: rv = QueryContentRect(IsPlugin(focusedContent) ? michael@0: focusedContent : mRootContent.get(), aEvent); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::OnQueryCaretRect(WidgetQueryContentEvent* aEvent) michael@0: { michael@0: nsresult rv = Init(aEvent); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: LineBreakType lineBreakType = GetLineBreakType(aEvent); michael@0: michael@0: nsRefPtr caret = mPresShell->GetCaret(); michael@0: NS_ASSERTION(caret, "GetCaret returned null"); michael@0: michael@0: // When the selection is collapsed and the queried offset is current caret michael@0: // position, we should return the "real" caret rect. michael@0: bool selectionIsCollapsed; michael@0: rv = mSelection->GetIsCollapsed(&selectionIsCollapsed); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (selectionIsCollapsed) { michael@0: uint32_t offset; michael@0: rv = GetFlatTextOffsetOfRange(mRootContent, mFirstSelectedRange, &offset, michael@0: lineBreakType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (offset == aEvent->mInput.mOffset) { michael@0: nsRect rect; michael@0: nsIFrame* caretFrame = caret->GetGeometry(mSelection, &rect); michael@0: if (!caretFrame) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: rv = ConvertToRootViewRelativeOffset(caretFrame, rect); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: aEvent->mReply.mRect = michael@0: rect.ToOutsidePixels(caretFrame->PresContext()->AppUnitsPerDevPixel()); michael@0: aEvent->mReply.mOffset = aEvent->mInput.mOffset; michael@0: aEvent->mSucceeded = true; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // Otherwise, we should set the guessed caret rect. michael@0: nsRefPtr range = new nsRange(mRootContent); michael@0: rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset, 0, michael@0: lineBreakType, true, michael@0: &aEvent->mReply.mOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: int32_t xpOffsetInFrame; michael@0: nsIFrame* frame; michael@0: rv = GetStartFrameAndOffset(range, &frame, &xpOffsetInFrame); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsPoint posInFrame; michael@0: rv = frame->GetPointFromOffset(range->StartOffset(), &posInFrame); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsRect rect; michael@0: rect.x = posInFrame.x; michael@0: rect.y = posInFrame.y; michael@0: rect.width = caret->GetCaretRect().width; michael@0: rect.height = frame->GetSize().height; michael@0: michael@0: rv = ConvertToRootViewRelativeOffset(frame, rect); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: aEvent->mReply.mRect = michael@0: rect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel()); michael@0: aEvent->mSucceeded = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::OnQueryContentState(WidgetQueryContentEvent* aEvent) michael@0: { michael@0: nsresult rv = Init(aEvent); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: aEvent->mSucceeded = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::OnQuerySelectionAsTransferable( michael@0: WidgetQueryContentEvent* aEvent) michael@0: { michael@0: nsresult rv = Init(aEvent); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: if (!aEvent->mReply.mHasSelection) { michael@0: aEvent->mSucceeded = true; michael@0: aEvent->mReply.mTransferable = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr doc = mPresShell->GetDocument(); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); michael@0: michael@0: rv = nsCopySupport::GetTransferableForSelection( michael@0: mSelection, doc, getter_AddRefs(aEvent->mReply.mTransferable)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: aEvent->mSucceeded = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::OnQueryCharacterAtPoint(WidgetQueryContentEvent* aEvent) michael@0: { michael@0: nsresult rv = Init(aEvent); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: nsIFrame* rootFrame = mPresShell->GetRootFrame(); michael@0: NS_ENSURE_TRUE(rootFrame, NS_ERROR_FAILURE); michael@0: nsIWidget* rootWidget = rootFrame->GetNearestWidget(); michael@0: NS_ENSURE_TRUE(rootWidget, NS_ERROR_FAILURE); michael@0: michael@0: // The root frame's widget might be different, e.g., the event was fired on michael@0: // a popup but the rootFrame is the document root. michael@0: if (rootWidget != aEvent->widget) { michael@0: NS_PRECONDITION(aEvent->widget, "The event must have the widget"); michael@0: nsView* view = nsView::GetViewFor(aEvent->widget); michael@0: NS_ENSURE_TRUE(view, NS_ERROR_FAILURE); michael@0: rootFrame = view->GetFrame(); michael@0: NS_ENSURE_TRUE(rootFrame, NS_ERROR_FAILURE); michael@0: rootWidget = rootFrame->GetNearestWidget(); michael@0: NS_ENSURE_TRUE(rootWidget, NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: WidgetQueryContentEvent eventOnRoot(true, NS_QUERY_CHARACTER_AT_POINT, michael@0: rootWidget); michael@0: eventOnRoot.mUseNativeLineBreak = aEvent->mUseNativeLineBreak; michael@0: eventOnRoot.refPoint = aEvent->refPoint; michael@0: if (rootWidget != aEvent->widget) { michael@0: eventOnRoot.refPoint += LayoutDeviceIntPoint::FromUntyped( michael@0: aEvent->widget->WidgetToScreenOffset() - michael@0: rootWidget->WidgetToScreenOffset()); michael@0: } michael@0: nsPoint ptInRoot = michael@0: nsLayoutUtils::GetEventCoordinatesRelativeTo(&eventOnRoot, rootFrame); michael@0: michael@0: nsIFrame* targetFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, ptInRoot); michael@0: if (!targetFrame || targetFrame->GetType() != nsGkAtoms::textFrame || michael@0: !targetFrame->GetContent() || michael@0: !nsContentUtils::ContentIsDescendantOf(targetFrame->GetContent(), michael@0: mRootContent)) { michael@0: // there is no character at the point. michael@0: aEvent->mReply.mOffset = WidgetQueryContentEvent::NOT_FOUND; michael@0: aEvent->mSucceeded = true; michael@0: return NS_OK; michael@0: } michael@0: nsPoint ptInTarget = ptInRoot + rootFrame->GetOffsetToCrossDoc(targetFrame); michael@0: int32_t rootAPD = rootFrame->PresContext()->AppUnitsPerDevPixel(); michael@0: int32_t targetAPD = targetFrame->PresContext()->AppUnitsPerDevPixel(); michael@0: ptInTarget = ptInTarget.ConvertAppUnits(rootAPD, targetAPD); michael@0: michael@0: nsTextFrame* textframe = static_cast(targetFrame); michael@0: nsIFrame::ContentOffsets contentOffsets = michael@0: textframe->GetCharacterOffsetAtFramePoint(ptInTarget); michael@0: NS_ENSURE_TRUE(contentOffsets.content, NS_ERROR_FAILURE); michael@0: uint32_t offset; michael@0: rv = GetFlatTextOffsetOfRange(mRootContent, contentOffsets.content, michael@0: contentOffsets.offset, &offset, michael@0: GetLineBreakType(aEvent)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: WidgetQueryContentEvent textRect(true, NS_QUERY_TEXT_RECT, aEvent->widget); michael@0: textRect.InitForQueryTextRect(offset, 1, aEvent->mUseNativeLineBreak); michael@0: rv = OnQueryTextRect(&textRect); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(textRect.mSucceeded, NS_ERROR_FAILURE); michael@0: michael@0: // currently, we don't need to get the actual text. michael@0: aEvent->mReply.mOffset = offset; michael@0: aEvent->mReply.mRect = textRect.mReply.mRect; michael@0: aEvent->mSucceeded = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::OnQueryDOMWidgetHittest(WidgetQueryContentEvent* aEvent) michael@0: { michael@0: NS_ASSERTION(aEvent, "aEvent must not be null"); michael@0: michael@0: nsresult rv = InitBasic(); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: aEvent->mSucceeded = false; michael@0: aEvent->mReply.mWidgetIsHit = false; michael@0: michael@0: NS_ENSURE_TRUE(aEvent->widget, NS_ERROR_FAILURE); michael@0: michael@0: nsIDocument* doc = mPresShell->GetDocument(); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); michael@0: nsIFrame* docFrame = mPresShell->GetRootFrame(); michael@0: NS_ENSURE_TRUE(docFrame, NS_ERROR_FAILURE); michael@0: michael@0: LayoutDeviceIntPoint eventLoc = aEvent->refPoint + michael@0: LayoutDeviceIntPoint::FromUntyped(aEvent->widget->WidgetToScreenOffset()); michael@0: nsIntRect docFrameRect = docFrame->GetScreenRect(); // Returns CSS pixels michael@0: CSSIntPoint eventLocCSS( michael@0: mPresContext->DevPixelsToIntCSSPixels(eventLoc.x) - docFrameRect.x, michael@0: mPresContext->DevPixelsToIntCSSPixels(eventLoc.y) - docFrameRect.y); michael@0: michael@0: Element* contentUnderMouse = michael@0: doc->ElementFromPointHelper(eventLocCSS.x, eventLocCSS.y, false, false); michael@0: if (contentUnderMouse) { michael@0: nsIWidget* targetWidget = nullptr; michael@0: nsIFrame* targetFrame = contentUnderMouse->GetPrimaryFrame(); michael@0: nsIObjectFrame* pluginFrame = do_QueryFrame(targetFrame); michael@0: if (pluginFrame) { michael@0: targetWidget = pluginFrame->GetWidget(); michael@0: } else if (targetFrame) { michael@0: targetWidget = targetFrame->GetNearestWidget(); michael@0: } michael@0: if (aEvent->widget == targetWidget) { michael@0: aEvent->mReply.mWidgetIsHit = true; michael@0: } michael@0: } michael@0: michael@0: aEvent->mSucceeded = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* static */ nsresult michael@0: ContentEventHandler::GetFlatTextOffsetOfRange(nsIContent* aRootContent, michael@0: nsINode* aNode, michael@0: int32_t aNodeOffset, michael@0: uint32_t* aOffset, michael@0: LineBreakType aLineBreakType) michael@0: { michael@0: NS_ENSURE_STATE(aRootContent); michael@0: NS_ASSERTION(aOffset, "param is invalid"); michael@0: michael@0: nsRefPtr prev = new nsRange(aRootContent); michael@0: nsCOMPtr rootDOMNode(do_QueryInterface(aRootContent)); michael@0: prev->SetStart(rootDOMNode, 0); michael@0: michael@0: nsCOMPtr startDOMNode(do_QueryInterface(aNode)); michael@0: NS_ASSERTION(startDOMNode, "startNode doesn't have nsIDOMNode"); michael@0: michael@0: nsCOMPtr iter = NS_NewContentIterator(); michael@0: michael@0: if (aNode->Length() >= static_cast(aNodeOffset)) { michael@0: // Offset is within node's length; set end of range to that offset michael@0: prev->SetEnd(startDOMNode, aNodeOffset); michael@0: iter->Init(prev); michael@0: } else if (aNode != static_cast(aRootContent)) { michael@0: // Offset is past node's length; set end of range to end of node michael@0: prev->SetEndAfter(startDOMNode); michael@0: iter->Init(prev); michael@0: } else { michael@0: // Offset is past the root node; set end of range to end of root node michael@0: iter->Init(aRootContent); michael@0: } michael@0: michael@0: nsCOMPtr startNode = do_QueryInterface(startDOMNode); michael@0: nsINode* endNode = aNode; michael@0: michael@0: *aOffset = 0; michael@0: for (; !iter->IsDone(); iter->Next()) { michael@0: nsINode* node = iter->GetCurrentNode(); michael@0: if (!node) { michael@0: break; michael@0: } michael@0: if (!node->IsNodeOfType(nsINode::eCONTENT)) { michael@0: continue; michael@0: } michael@0: nsIContent* content = static_cast(node); michael@0: michael@0: if (node->IsNodeOfType(nsINode::eTEXT)) { michael@0: // Note: our range always starts from offset 0 michael@0: if (node == endNode) { michael@0: *aOffset += GetTextLength(content, aLineBreakType, aNodeOffset); michael@0: } else { michael@0: *aOffset += GetTextLength(content, aLineBreakType); michael@0: } michael@0: } else if (IsContentBR(content)) { michael@0: #if defined(XP_WIN) michael@0: // On Windows, the length of the newline is 2. michael@0: *aOffset += (aLineBreakType == LINE_BREAK_TYPE_NATIVE) ? 2 : 1; michael@0: #else michael@0: // On other platforms, the length of the newline is 1. michael@0: *aOffset += 1; michael@0: #endif michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* static */ nsresult michael@0: ContentEventHandler::GetFlatTextOffsetOfRange(nsIContent* aRootContent, michael@0: nsRange* aRange, michael@0: uint32_t* aOffset, michael@0: LineBreakType aLineBreakType) michael@0: { michael@0: nsINode* startNode = aRange->GetStartParent(); michael@0: NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); michael@0: int32_t startOffset = aRange->StartOffset(); michael@0: return GetFlatTextOffsetOfRange(aRootContent, startNode, startOffset, michael@0: aOffset, aLineBreakType); michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::GetStartFrameAndOffset(nsRange* aRange, michael@0: nsIFrame** aFrame, michael@0: int32_t* aOffsetInFrame) michael@0: { michael@0: NS_ASSERTION(aRange && aFrame && aOffsetInFrame, "params are invalid"); michael@0: michael@0: nsIContent* content = nullptr; michael@0: nsINode* node = aRange->GetStartParent(); michael@0: if (node && node->IsNodeOfType(nsINode::eCONTENT)) { michael@0: content = static_cast(node); michael@0: } michael@0: NS_ASSERTION(content, "the start node doesn't have nsIContent!"); michael@0: michael@0: nsRefPtr fs = mPresShell->FrameSelection(); michael@0: *aFrame = fs->GetFrameForNodeOffset(content, aRange->StartOffset(), michael@0: fs->GetHint(), aOffsetInFrame); michael@0: NS_ENSURE_TRUE((*aFrame), NS_ERROR_FAILURE); michael@0: NS_ASSERTION((*aFrame)->GetType() == nsGkAtoms::textFrame, michael@0: "The frame is not textframe"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::ConvertToRootViewRelativeOffset(nsIFrame* aFrame, michael@0: nsRect& aRect) michael@0: { michael@0: NS_ASSERTION(aFrame, "aFrame must not be null"); michael@0: michael@0: nsView* view = nullptr; michael@0: nsPoint posInView; michael@0: aFrame->GetOffsetFromView(posInView, &view); michael@0: if (!view) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: aRect += posInView + view->GetOffsetTo(nullptr); michael@0: return NS_OK; michael@0: } michael@0: michael@0: static void AdjustRangeForSelection(nsIContent* aRoot, michael@0: nsINode** aNode, michael@0: int32_t* aNodeOffset) michael@0: { michael@0: nsINode* node = *aNode; michael@0: int32_t nodeOffset = *aNodeOffset; michael@0: if (aRoot != node && node->GetParent()) { michael@0: if (node->IsNodeOfType(nsINode::eTEXT)) { michael@0: // When the offset is at the end of the text node, set it to after the michael@0: // text node, to make sure the caret is drawn on a new line when the last michael@0: // character of the text node is '\n' michael@0: int32_t nodeLength = michael@0: static_cast(static_cast(node)->TextLength()); michael@0: MOZ_ASSERT(nodeOffset <= nodeLength, "Offset is past length of text node"); michael@0: if (nodeOffset == nodeLength) { michael@0: node = node->GetParent(); michael@0: nodeOffset = node->IndexOf(*aNode) + 1; michael@0: } michael@0: } else { michael@0: node = node->GetParent(); michael@0: nodeOffset = node->IndexOf(*aNode) + (nodeOffset ? 1 : 0); michael@0: } michael@0: } michael@0: michael@0: nsIContent* brContent = node->GetChildAt(nodeOffset - 1); michael@0: while (brContent && brContent->IsHTML()) { michael@0: if (brContent->Tag() != nsGkAtoms::br || IsContentBR(brContent)) { michael@0: break; michael@0: } michael@0: brContent = node->GetChildAt(--nodeOffset - 1); michael@0: } michael@0: *aNode = node; michael@0: *aNodeOffset = std::max(nodeOffset, 0); michael@0: } michael@0: michael@0: nsresult michael@0: ContentEventHandler::OnSelectionEvent(WidgetSelectionEvent* aEvent) michael@0: { michael@0: aEvent->mSucceeded = false; michael@0: michael@0: // Get selection to manipulate michael@0: // XXX why do we need to get them from ISM? This method should work fine michael@0: // without ISM. michael@0: nsresult rv = michael@0: IMEStateManager::GetFocusSelectionAndRoot(getter_AddRefs(mSelection), michael@0: getter_AddRefs(mRootContent)); michael@0: if (rv != NS_ERROR_NOT_AVAILABLE) { michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else { michael@0: rv = Init(aEvent); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Get range from offset and length michael@0: nsRefPtr range = new nsRange(mRootContent); michael@0: rv = SetRangeFromFlatTextOffset(range, aEvent->mOffset, aEvent->mLength, michael@0: GetLineBreakType(aEvent), michael@0: aEvent->mExpandToClusterBoundary); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsINode* startNode = range->GetStartParent(); michael@0: nsINode* endNode = range->GetEndParent(); michael@0: int32_t startNodeOffset = range->StartOffset(); michael@0: int32_t endNodeOffset = range->EndOffset(); michael@0: AdjustRangeForSelection(mRootContent, &startNode, &startNodeOffset); michael@0: AdjustRangeForSelection(mRootContent, &endNode, &endNodeOffset); michael@0: michael@0: nsCOMPtr startDomNode(do_QueryInterface(startNode)); michael@0: nsCOMPtr endDomNode(do_QueryInterface(endNode)); michael@0: NS_ENSURE_TRUE(startDomNode && endDomNode, NS_ERROR_UNEXPECTED); michael@0: michael@0: nsCOMPtr selPrivate(do_QueryInterface(mSelection)); michael@0: selPrivate->StartBatchChanges(); michael@0: michael@0: // Clear selection first before setting michael@0: rv = mSelection->RemoveAllRanges(); michael@0: // Need to call EndBatchChanges at the end even if call failed michael@0: if (NS_SUCCEEDED(rv)) { michael@0: if (aEvent->mReversed) { michael@0: rv = mSelection->Collapse(endDomNode, endNodeOffset); michael@0: } else { michael@0: rv = mSelection->Collapse(startDomNode, startNodeOffset); michael@0: } michael@0: if (NS_SUCCEEDED(rv) && michael@0: (startDomNode != endDomNode || startNodeOffset != endNodeOffset)) { michael@0: if (aEvent->mReversed) { michael@0: rv = mSelection->Extend(startDomNode, startNodeOffset); michael@0: } else { michael@0: rv = mSelection->Extend(endDomNode, endNodeOffset); michael@0: } michael@0: } michael@0: } michael@0: selPrivate->EndBatchChanges(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: selPrivate->ScrollIntoViewInternal( michael@0: nsISelectionController::SELECTION_FOCUS_REGION, michael@0: false, nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis()); michael@0: aEvent->mSucceeded = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace mozilla