1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=80: */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is mozilla.org code.
18 * The Initial Developer of the Original Code is
20 * Portions created by the Initial Developer are Copyright (C) 2008
21 * the Initial Developer. All Rights Reserved.
24 * Masayuki Nakano <masayuki@d-toybox.com>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #include "nsQueryContentEventHandler.h"
42 #include "nsPresContext.h"
43 #include "nsIPresShell.h"
44 #include "nsISelection.h"
45 #include "nsIDOMText.h"
46 #include "nsIDOMRange.h"
48 #include "nsGUIEvent.h"
50 #include "nsFrameSelection.h"
53 #include "nsIContentIterator.h"
54 #include "nsTextFragment.h"
55 #include "nsTextFrame.h"
57 nsresult
NS_NewContentIterator(nsIContentIterator
** aInstancePtrResult
);
59 /******************************************************************/
60 /* nsQueryContentEventHandler */
61 /******************************************************************/
63 nsQueryContentEventHandler::nsQueryContentEventHandler(
64 nsPresContext
* aPresContext
) :
65 mPresContext(aPresContext
),
66 mPresShell(aPresContext
->GetPresShell()), mSelection(nsnull
),
67 mFirstSelectedRange(nsnull
), mRootContent(nsnull
)
72 nsQueryContentEventHandler::Init(nsQueryContentEvent
* aEvent
)
74 NS_ASSERTION(aEvent
, "aEvent must not be null");
79 aEvent
->mSucceeded
= PR_FALSE
;
82 return NS_ERROR_NOT_AVAILABLE
;
84 nsresult rv
= mPresShell
->GetSelectionForCopy(getter_AddRefs(mSelection
));
85 NS_ENSURE_SUCCESS(rv
, rv
);
86 NS_ASSERTION(mSelection
,
87 "GetSelectionForCopy succeeded, but the result is null");
89 nsCOMPtr
<nsIDOMRange
> firstRange
;
90 rv
= mSelection
->GetRangeAt(0, getter_AddRefs(firstRange
));
91 // This shell doesn't support selection.
93 return NS_ERROR_NOT_AVAILABLE
;
94 mFirstSelectedRange
= do_QueryInterface(firstRange
);
95 NS_ENSURE_TRUE(mFirstSelectedRange
, NS_ERROR_FAILURE
);
97 nsINode
* startNode
= mFirstSelectedRange
->GetStartParent();
98 NS_ENSURE_TRUE(startNode
, NS_ERROR_FAILURE
);
99 mRootContent
= startNode
->GetSelectionRootContent(mPresShell
);
100 NS_ENSURE_TRUE(mRootContent
, NS_ERROR_FAILURE
);
102 aEvent
->mReply
.mContentsRoot
= mRootContent
.get();
107 static void ConvertToNativeNewlines(nsAFlatString
& aString
)
109 #if defined(XP_MACOSX)
110 aString
.ReplaceSubstring(NS_LITERAL_STRING("\n"), NS_LITERAL_STRING("\r"));
111 #elif defined(XP_WIN)
112 aString
.ReplaceSubstring(NS_LITERAL_STRING("\n"), NS_LITERAL_STRING("\r\n"));
116 static void ConvertToXPNewlines(nsAFlatString
& aString
)
118 #if defined(XP_MACOSX)
119 aString
.ReplaceSubstring(NS_LITERAL_STRING("\r"), NS_LITERAL_STRING("\n"));
120 #elif defined(XP_WIN)
121 aString
.ReplaceSubstring(NS_LITERAL_STRING("\r\n"), NS_LITERAL_STRING("\n"));
125 static void AppendString(nsAString
& aString
, nsIContent
* aContent
)
127 NS_ASSERTION(aContent
->IsNodeOfType(nsINode::eTEXT
),
128 "aContent is not a text node!");
129 const nsTextFragment
* text
= aContent
->GetText();
132 text
->AppendTo(aString
);
135 static void AppendSubString(nsAString
& aString
, nsIContent
* aContent
,
136 PRUint32 aXPOffset
, PRUint32 aXPLength
)
138 NS_ASSERTION(aContent
->IsNodeOfType(nsINode::eTEXT
),
139 "aContent is not a text node!");
140 const nsTextFragment
* text
= aContent
->GetText();
143 text
->AppendTo(aString
, PRInt32(aXPOffset
), PRInt32(aXPLength
));
146 static PRUint32
GetNativeTextLength(nsIContent
* aContent
)
149 if (aContent
->IsNodeOfType(nsINode::eTEXT
))
150 AppendString(str
, aContent
);
151 else if (aContent
->IsNodeOfType(nsINode::eHTML
) &&
152 aContent
->Tag() == nsGkAtoms::br
)
153 str
.Assign(PRUnichar('\n'));
154 ConvertToNativeNewlines(str
);
158 static PRUint32
ConvertToXPOffset(nsIContent
* aContent
, PRUint32 aNativeOffset
)
162 AppendString(str
, aContent
);
163 ConvertToNativeNewlines(str
);
164 NS_ASSERTION(aNativeOffset
<= str
.Length(),
165 "aOffsetForNativeLF is too large!");
166 str
.Truncate(aNativeOffset
);
167 ConvertToXPNewlines(str
);
172 nsQueryContentEventHandler::GenerateFlatTextContent(nsIRange
* aRange
,
173 nsAFlatString
& aString
)
175 nsCOMPtr
<nsIContentIterator
> iter
;
176 nsresult rv
= NS_NewContentIterator(getter_AddRefs(iter
));
177 NS_ENSURE_SUCCESS(rv
, rv
);
178 NS_ASSERTION(iter
, "NS_NewContentIterator succeeded, but the result is null");
179 nsCOMPtr
<nsIDOMRange
> domRange(do_QueryInterface(aRange
));
180 NS_ASSERTION(domRange
, "aRange doesn't have nsIDOMRange!");
181 iter
->Init(domRange
);
183 NS_ASSERTION(aString
.IsEmpty(), "aString must be empty string");
185 nsINode
* startNode
= aRange
->GetStartParent();
186 nsINode
* endNode
= aRange
->GetEndParent();
188 if (startNode
== endNode
&& startNode
->IsNodeOfType(nsINode::eTEXT
)) {
189 nsIContent
* content
= static_cast<nsIContent
*>(startNode
);
190 AppendSubString(aString
, content
, aRange
->StartOffset(),
191 aRange
->EndOffset() - aRange
->StartOffset());
192 ConvertToNativeNewlines(aString
);
197 for (; !iter
->IsDone(); iter
->Next()) {
198 nsIContent
* content
= iter
->GetCurrentNode();
202 if (content
->IsNodeOfType(nsINode::eTEXT
)) {
203 if (content
== startNode
)
204 AppendSubString(aString
, content
, aRange
->StartOffset(),
205 content
->TextLength() - aRange
->StartOffset());
206 else if (content
== endNode
)
207 AppendSubString(aString
, content
, 0, aRange
->EndOffset());
209 AppendString(aString
, content
);
210 } else if (content
->IsNodeOfType(nsINode::eHTML
) &&
211 content
->Tag() == nsGkAtoms::br
)
212 aString
.Append(PRUnichar('\n'));
214 ConvertToNativeNewlines(aString
);
219 nsQueryContentEventHandler::ExpandToClusterBoundary(nsIContent
* aContent
,
223 NS_ASSERTION(*aXPOffset
>= 0 && *aXPOffset
<= aContent
->TextLength(),
224 "offset is out of range.");
226 // XXX This method assumes that the frame boundaries must be cluster
227 // boundaries. It's false, but no problem now, maybe.
228 if (!aContent
->IsNodeOfType(nsINode::eTEXT
) ||
229 *aXPOffset
== 0 || *aXPOffset
== aContent
->TextLength())
231 nsCOMPtr
<nsFrameSelection
> fs
= mPresShell
->FrameSelection();
232 PRInt32 offsetInFrame
;
233 nsFrameSelection::HINT hint
=
234 aForward
? nsFrameSelection::HINTLEFT
: nsFrameSelection::HINTRIGHT
;
235 nsIFrame
* frame
= fs
->GetFrameForNodeOffset(aContent
, PRInt32(*aXPOffset
),
236 hint
, &offsetInFrame
);
238 // This content doesn't have any frames, we only can check surrogate pair...
239 const nsTextFragment
* text
= aContent
->GetText();
240 NS_ENSURE_TRUE(text
, NS_ERROR_FAILURE
);
241 if (NS_IS_LOW_SURROGATE(text
->CharAt(*aXPOffset
)) &&
242 NS_IS_HIGH_SURROGATE(text
->CharAt(*aXPOffset
- 1)))
243 *aXPOffset
+= aForward
? 1 : -1;
246 PRInt32 startOffset
, endOffset
;
247 nsresult rv
= frame
->GetOffsets(startOffset
, endOffset
);
248 NS_ENSURE_SUCCESS(rv
, rv
);
249 if (*aXPOffset
== PRUint32(startOffset
) || *aXPOffset
== PRUint32(endOffset
))
251 if (frame
->GetType() != nsGkAtoms::textFrame
)
252 return NS_ERROR_FAILURE
;
253 nsTextFrame
* textFrame
= static_cast<nsTextFrame
*>(frame
);
254 PRInt32 newOffsetInFrame
= offsetInFrame
;
255 newOffsetInFrame
+= aForward
? -1 : 1;
256 textFrame
->PeekOffsetCharacter(aForward
, &newOffsetInFrame
);
257 *aXPOffset
= startOffset
+ newOffsetInFrame
;
262 nsQueryContentEventHandler::SetRangeFromFlatTextOffset(
264 PRUint32 aNativeOffset
,
265 PRUint32 aNativeLength
,
266 PRBool aExpandToClusterBoundaries
)
268 nsCOMPtr
<nsIContentIterator
> iter
;
269 nsresult rv
= NS_NewContentIterator(getter_AddRefs(iter
));
270 NS_ENSURE_SUCCESS(rv
, rv
);
271 NS_ASSERTION(iter
, "NS_NewContentIterator succeeded, but the result is null");
272 rv
= iter
->Init(mRootContent
);
273 NS_ENSURE_SUCCESS(rv
, rv
);
274 nsCOMPtr
<nsIDOMRange
> domRange(do_QueryInterface(aRange
));
275 NS_ASSERTION(domRange
, "aRange doesn't have nsIDOMRange!");
277 PRUint32 nativeOffset
= 0;
278 PRUint32 nativeEndOffset
= aNativeOffset
+ aNativeLength
;
279 nsIContent
* content
= nsnull
;
280 for (; !iter
->IsDone(); iter
->Next()) {
281 content
= iter
->GetCurrentNode();
285 PRUint32 nativeTextLength
;
286 nativeTextLength
= GetNativeTextLength(content
);
287 if (nativeTextLength
== 0)
290 if (nativeOffset
<= aNativeOffset
&&
291 aNativeOffset
< nativeOffset
+ nativeTextLength
) {
292 nsCOMPtr
<nsIDOMNode
> domNode(do_QueryInterface(content
));
293 NS_ASSERTION(domNode
, "aContent doesn't have nsIDOMNode!");
296 content
->IsNodeOfType(nsINode::eTEXT
) ?
297 ConvertToXPOffset(content
, aNativeOffset
- nativeOffset
) : 0;
299 if (aExpandToClusterBoundaries
) {
300 rv
= ExpandToClusterBoundary(content
, PR_FALSE
, &xpOffset
);
301 NS_ENSURE_SUCCESS(rv
, rv
);
304 rv
= domRange
->SetStart(domNode
, PRInt32(xpOffset
));
305 NS_ENSURE_SUCCESS(rv
, rv
);
306 if (aNativeLength
== 0) {
307 // Ensure that the end offset and the start offset are same.
308 rv
= domRange
->SetEnd(domNode
, PRInt32(xpOffset
));
309 NS_ENSURE_SUCCESS(rv
, rv
);
313 if (nativeEndOffset
<= nativeOffset
+ nativeTextLength
) {
314 nsCOMPtr
<nsIDOMNode
> domNode(do_QueryInterface(content
));
315 NS_ASSERTION(domNode
, "aContent doesn't have nsIDOMNode!");
318 if (content
->IsNodeOfType(nsINode::eTEXT
)) {
319 xpOffset
= ConvertToXPOffset(content
, nativeEndOffset
- nativeOffset
);
320 if (aExpandToClusterBoundaries
) {
321 rv
= ExpandToClusterBoundary(content
, PR_TRUE
, &xpOffset
);
322 NS_ENSURE_SUCCESS(rv
, rv
);
325 // Use first position of next node, because the end node is ignored
326 // by ContentIterator when the offset is zero.
331 domNode
= do_QueryInterface(iter
->GetCurrentNode());
334 rv
= domRange
->SetEnd(domNode
, PRInt32(xpOffset
));
335 NS_ENSURE_SUCCESS(rv
, rv
);
339 nativeOffset
+= nativeTextLength
;
342 if (nativeOffset
< aNativeOffset
)
343 return NS_ERROR_FAILURE
;
345 nsCOMPtr
<nsIDOMNode
> domNode(do_QueryInterface(mRootContent
));
346 NS_ASSERTION(domNode
, "lastContent doesn't have nsIDOMNode!");
348 rv
= domRange
->SetStart(domNode
, 0);
349 NS_ENSURE_SUCCESS(rv
, rv
);
351 rv
= domRange
->SetEnd(domNode
, PRInt32(mRootContent
->GetChildCount()));
352 NS_ASSERTION(NS_SUCCEEDED(rv
), "nsIDOMRange::SetEnd failed");
357 nsQueryContentEventHandler::OnQuerySelectedText(nsQueryContentEvent
* aEvent
)
359 nsresult rv
= Init(aEvent
);
363 NS_ASSERTION(aEvent
->mReply
.mString
.IsEmpty(),
364 "The reply string must be empty");
366 rv
= GetFlatTextOffsetOfRange(mFirstSelectedRange
, &aEvent
->mReply
.mOffset
);
367 NS_ENSURE_SUCCESS(rv
, rv
);
370 rv
= mSelection
->GetIsCollapsed(&isCollapsed
);
371 NS_ENSURE_SUCCESS(rv
, rv
);
374 nsCOMPtr
<nsIDOMRange
> domRange
;
375 rv
= mSelection
->GetRangeAt(0, getter_AddRefs(domRange
));
376 NS_ENSURE_SUCCESS(rv
, rv
);
377 NS_ASSERTION(domRange
, "GetRangeAt succeeded, but the result is null");
379 nsCOMPtr
<nsIRange
> range(do_QueryInterface(domRange
));
380 NS_ENSURE_TRUE(range
, NS_ERROR_FAILURE
);
381 rv
= GenerateFlatTextContent(range
, aEvent
->mReply
.mString
);
382 NS_ENSURE_SUCCESS(rv
, rv
);
385 aEvent
->mSucceeded
= PR_TRUE
;
390 nsQueryContentEventHandler::OnQueryTextContent(nsQueryContentEvent
* aEvent
)
392 nsresult rv
= Init(aEvent
);
396 NS_ASSERTION(aEvent
->mReply
.mString
.IsEmpty(),
397 "The reply string must be empty");
399 nsCOMPtr
<nsIRange
> range
= new nsRange();
400 NS_ENSURE_TRUE(range
, NS_ERROR_OUT_OF_MEMORY
);
401 rv
= SetRangeFromFlatTextOffset(range
, aEvent
->mInput
.mOffset
,
402 aEvent
->mInput
.mLength
, PR_FALSE
);
403 NS_ENSURE_SUCCESS(rv
, rv
);
405 rv
= GenerateFlatTextContent(range
, aEvent
->mReply
.mString
);
406 NS_ENSURE_SUCCESS(rv
, rv
);
408 aEvent
->mSucceeded
= PR_TRUE
;
414 nsQueryContentEventHandler::QueryRectFor(nsQueryContentEvent
* aEvent
,
418 PRInt32 offsetInFrame
;
420 nsresult rv
= GetStartFrameAndOffset(aRange
, &frame
, &offsetInFrame
);
421 NS_ENSURE_SUCCESS(rv
, rv
);
424 rv
= frame
->GetPointFromOffset(aRange
->StartOffset(), &posInFrame
);
425 NS_ENSURE_SUCCESS(rv
, rv
);
427 aEvent
->mReply
.mRect
.y
= posInFrame
.y
;
428 aEvent
->mReply
.mRect
.height
= frame
->GetSize().height
;
430 if (aEvent
->message
== NS_QUERY_CHARACTER_RECT
) {
432 rv
= frame
->GetPointFromOffset(aRange
->EndOffset(), &nextPos
);
433 NS_ENSURE_SUCCESS(rv
, rv
);
434 aEvent
->mReply
.mRect
.x
= PR_MIN(posInFrame
.x
, nextPos
.x
);
435 aEvent
->mReply
.mRect
.width
= PR_ABS(posInFrame
.x
- nextPos
.x
);
437 aEvent
->mReply
.mRect
.x
= posInFrame
.x
;
438 aEvent
->mReply
.mRect
.width
= aCaret
->GetCaretRect().width
;
441 // The coordinates are app units here, they will be converted to system
442 // coordinates by view manager.
443 rv
= ConvertToRootViewRelativeOffset(frame
, aEvent
->mReply
.mRect
);
444 NS_ENSURE_SUCCESS(rv
, rv
);
446 aEvent
->mSucceeded
= PR_TRUE
;
451 nsQueryContentEventHandler::OnQueryCharacterRect(nsQueryContentEvent
* aEvent
)
453 nsresult rv
= Init(aEvent
);
457 nsCOMPtr
<nsIRange
> range
= new nsRange();
458 NS_ENSURE_TRUE(range
, NS_ERROR_OUT_OF_MEMORY
);
459 rv
= SetRangeFromFlatTextOffset(range
, aEvent
->mInput
.mOffset
, 1, PR_TRUE
);
460 NS_ENSURE_SUCCESS(rv
, rv
);
462 if (range
->Collapsed()) {
463 // There is no character at the offset.
467 return QueryRectFor(aEvent
, range
, nsnull
);
471 nsQueryContentEventHandler::OnQueryCaretRect(nsQueryContentEvent
* aEvent
)
473 nsresult rv
= Init(aEvent
);
477 nsCOMPtr
<nsICaret
> caret
;
478 rv
= mPresShell
->GetCaret(getter_AddRefs(caret
));
479 NS_ENSURE_SUCCESS(rv
, rv
);
480 NS_ASSERTION(caret
, "GetCaret succeeded, but the result is null");
482 // When the selection is collapsed and the queried offset is current caret
483 // position, we should return the "real" caret rect.
484 PRBool selectionIsCollapsed
;
485 rv
= mSelection
->GetIsCollapsed(&selectionIsCollapsed
);
486 NS_ENSURE_SUCCESS(rv
, rv
);
488 if (selectionIsCollapsed
) {
490 rv
= GetFlatTextOffsetOfRange(mFirstSelectedRange
, &offset
);
491 NS_ENSURE_SUCCESS(rv
, rv
);
492 if (offset
== aEvent
->mInput
.mOffset
) {
494 rv
= caret
->GetCaretCoordinates(nsICaret::eTopLevelWindowCoordinates
,
495 mSelection
, &aEvent
->mReply
.mRect
,
496 &isCollapsed
, nsnull
);
497 NS_ENSURE_SUCCESS(rv
, rv
);
498 aEvent
->mSucceeded
= PR_TRUE
;
503 // Otherwise, we should set the guessed caret rect.
504 nsCOMPtr
<nsIRange
> range
= new nsRange();
505 NS_ENSURE_TRUE(range
, NS_ERROR_OUT_OF_MEMORY
);
506 rv
= SetRangeFromFlatTextOffset(range
, aEvent
->mInput
.mOffset
, 0, PR_TRUE
);
507 NS_ENSURE_SUCCESS(rv
, rv
);
509 return QueryRectFor(aEvent
, range
, caret
);
513 nsQueryContentEventHandler::GetFlatTextOffsetOfRange(nsIRange
* aRange
,
514 PRUint32
* aNativeOffset
)
516 NS_ASSERTION(aNativeOffset
, "param is invalid");
518 nsCOMPtr
<nsIRange
> prev
= new nsRange();
519 NS_ENSURE_TRUE(prev
, NS_ERROR_OUT_OF_MEMORY
);
520 nsCOMPtr
<nsIDOMRange
> domPrev(do_QueryInterface(prev
));
521 NS_ASSERTION(domPrev
, "nsRange doesn't have nsIDOMRange??");
522 nsCOMPtr
<nsIDOMNode
> rootDOMNode(do_QueryInterface(mRootContent
));
523 domPrev
->SetStart(rootDOMNode
, 0);
525 nsINode
* startNode
= aRange
->GetStartParent();
526 NS_ENSURE_TRUE(startNode
, NS_ERROR_FAILURE
);
528 PRInt32 startOffset
= aRange
->StartOffset();
529 nsCOMPtr
<nsIDOMNode
> startDOMNode(do_QueryInterface(startNode
));
530 NS_ASSERTION(startDOMNode
, "startNode doesn't have nsIDOMNode");
531 domPrev
->SetEnd(startDOMNode
, startOffset
);
533 nsAutoString prevStr
;
534 nsresult rv
= GenerateFlatTextContent(prev
, prevStr
);
535 NS_ENSURE_SUCCESS(rv
, rv
);
536 *aNativeOffset
= prevStr
.Length();
541 nsQueryContentEventHandler::GetStartFrameAndOffset(nsIRange
* aRange
,
543 PRInt32
* aOffsetInFrame
)
545 NS_ASSERTION(aRange
&& aFrame
&& aOffsetInFrame
, "params are invalid");
547 nsIContent
* content
= nsnull
;
548 nsINode
* node
= aRange
->GetStartParent();
549 if (node
&& node
->IsNodeOfType(nsINode::eCONTENT
))
550 content
= static_cast<nsIContent
*>(node
);
551 NS_ASSERTION(content
, "the start node doesn't have nsIContent!");
553 nsCOMPtr
<nsFrameSelection
> fs
= mPresShell
->FrameSelection();
554 *aFrame
= fs
->GetFrameForNodeOffset(content
, aRange
->StartOffset(),
555 fs
->GetHint(), aOffsetInFrame
);
556 NS_ENSURE_TRUE((*aFrame
), NS_ERROR_FAILURE
);
557 NS_ASSERTION((*aFrame
)->GetType() == nsGkAtoms::textFrame
,
558 "The frame is not textframe");
563 nsQueryContentEventHandler::ConvertToRootViewRelativeOffset(nsIFrame
* aFrame
,
566 NS_ASSERTION(aFrame
, "aFrame must not be null");
568 nsIView
* view
= nsnull
;
570 aFrame
->GetOffsetFromView(posInView
, &view
);
572 return NS_ERROR_FAILURE
;
573 aRect
+= posInView
+ view
->GetOffsetTo(nsnull
);