|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=2 sw=2 et tw=78: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 /* |
|
8 * Implementation of selection: nsISelection,nsISelectionPrivate and nsFrameSelection |
|
9 */ |
|
10 |
|
11 #include "mozilla/dom/Selection.h" |
|
12 |
|
13 #include "mozilla/Attributes.h" |
|
14 #include "mozilla/EventStates.h" |
|
15 |
|
16 #include "nsCOMPtr.h" |
|
17 #include "nsString.h" |
|
18 #include "nsFrameSelection.h" |
|
19 #include "nsISelectionListener.h" |
|
20 #include "nsContentCID.h" |
|
21 #include "nsIContent.h" |
|
22 #include "nsIDOMNode.h" |
|
23 #include "nsRange.h" |
|
24 #include "nsCOMArray.h" |
|
25 #include "nsIDOMKeyEvent.h" |
|
26 #include "nsITableCellLayout.h" |
|
27 #include "nsTArray.h" |
|
28 #include "nsTableOuterFrame.h" |
|
29 #include "nsTableCellFrame.h" |
|
30 #include "nsIScrollableFrame.h" |
|
31 #include "nsCCUncollectableMarker.h" |
|
32 #include "nsIContentIterator.h" |
|
33 #include "nsIDocumentEncoder.h" |
|
34 #include "nsTextFragment.h" |
|
35 #include <algorithm> |
|
36 |
|
37 #include "nsGkAtoms.h" |
|
38 #include "nsIFrameTraversal.h" |
|
39 #include "nsLayoutUtils.h" |
|
40 #include "nsLayoutCID.h" |
|
41 #include "nsBidiPresUtils.h" |
|
42 static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID); |
|
43 #include "nsTextFrame.h" |
|
44 |
|
45 #include "nsIDOMText.h" |
|
46 |
|
47 #include "nsContentUtils.h" |
|
48 #include "nsThreadUtils.h" |
|
49 #include "mozilla/Preferences.h" |
|
50 #include "nsDOMClassInfoID.h" |
|
51 |
|
52 //included for desired x position; |
|
53 #include "nsPresContext.h" |
|
54 #include "nsIPresShell.h" |
|
55 #include "nsCaret.h" |
|
56 |
|
57 #include "mozilla/MouseEvents.h" |
|
58 #include "mozilla/TextEvents.h" |
|
59 |
|
60 #include "nsITimer.h" |
|
61 #include "nsFrameManager.h" |
|
62 // notifications |
|
63 #include "nsIDOMDocument.h" |
|
64 #include "nsIDocument.h" |
|
65 |
|
66 #include "nsISelectionController.h"//for the enums |
|
67 #include "nsAutoCopyListener.h" |
|
68 #include "nsCopySupport.h" |
|
69 #include "nsIClipboard.h" |
|
70 #include "nsIFrameInlines.h" |
|
71 |
|
72 #include "nsIBidiKeyboard.h" |
|
73 |
|
74 #include "nsError.h" |
|
75 #include "mozilla/dom/Element.h" |
|
76 #include "mozilla/dom/ShadowRoot.h" |
|
77 #include "mozilla/ErrorResult.h" |
|
78 #include "mozilla/dom/SelectionBinding.h" |
|
79 |
|
80 using namespace mozilla; |
|
81 using namespace mozilla::dom; |
|
82 |
|
83 //#define DEBUG_TABLE 1 |
|
84 |
|
85 static bool IsValidSelectionPoint(nsFrameSelection *aFrameSel, nsINode *aNode); |
|
86 |
|
87 static nsIAtom *GetTag(nsINode *aNode); |
|
88 // returns the parent |
|
89 static nsINode* ParentOffset(nsINode *aNode, int32_t *aChildOffset); |
|
90 static nsINode* GetCellParent(nsINode *aDomNode); |
|
91 |
|
92 #ifdef PRINT_RANGE |
|
93 static void printRange(nsRange *aDomRange); |
|
94 #define DEBUG_OUT_RANGE(x) printRange(x) |
|
95 #else |
|
96 #define DEBUG_OUT_RANGE(x) |
|
97 #endif // PRINT_RANGE |
|
98 |
|
99 |
|
100 |
|
101 //#define DEBUG_SELECTION // uncomment for printf describing every collapse and extend. |
|
102 //#define DEBUG_NAVIGATION |
|
103 |
|
104 |
|
105 //#define DEBUG_TABLE_SELECTION 1 |
|
106 |
|
107 struct CachedOffsetForFrame { |
|
108 CachedOffsetForFrame() |
|
109 : mCachedFrameOffset(0, 0) // nsPoint ctor |
|
110 , mLastCaretFrame(nullptr) |
|
111 , mLastContentOffset(0) |
|
112 , mCanCacheFrameOffset(false) |
|
113 {} |
|
114 |
|
115 nsPoint mCachedFrameOffset; // cached frame offset |
|
116 nsIFrame* mLastCaretFrame; // store the frame the caret was last drawn in. |
|
117 int32_t mLastContentOffset; // store last content offset |
|
118 bool mCanCacheFrameOffset; // cached frame offset is valid? |
|
119 }; |
|
120 |
|
121 // Stack-class to turn on/off selection batching for table selection |
|
122 class MOZ_STACK_CLASS nsSelectionBatcher MOZ_FINAL |
|
123 { |
|
124 private: |
|
125 nsCOMPtr<nsISelectionPrivate> mSelection; |
|
126 public: |
|
127 nsSelectionBatcher(nsISelectionPrivate *aSelection) : mSelection(aSelection) |
|
128 { |
|
129 if (mSelection) mSelection->StartBatchChanges(); |
|
130 } |
|
131 ~nsSelectionBatcher() |
|
132 { |
|
133 if (mSelection) mSelection->EndBatchChanges(); |
|
134 } |
|
135 }; |
|
136 |
|
137 class nsAutoScrollTimer : public nsITimerCallback |
|
138 { |
|
139 public: |
|
140 |
|
141 NS_DECL_ISUPPORTS |
|
142 |
|
143 nsAutoScrollTimer() |
|
144 : mFrameSelection(0), mSelection(0), mPresContext(0), mPoint(0,0), mDelay(30) |
|
145 { |
|
146 } |
|
147 |
|
148 virtual ~nsAutoScrollTimer() |
|
149 { |
|
150 if (mTimer) |
|
151 mTimer->Cancel(); |
|
152 } |
|
153 |
|
154 // aPoint is relative to aPresContext's root frame |
|
155 nsresult Start(nsPresContext *aPresContext, nsPoint &aPoint) |
|
156 { |
|
157 mPoint = aPoint; |
|
158 |
|
159 // Store the presentation context. The timer will be |
|
160 // stopped by the selection if the prescontext is destroyed. |
|
161 mPresContext = aPresContext; |
|
162 |
|
163 mContent = nsIPresShell::GetCapturingContent(); |
|
164 |
|
165 if (!mTimer) |
|
166 { |
|
167 nsresult result; |
|
168 mTimer = do_CreateInstance("@mozilla.org/timer;1", &result); |
|
169 |
|
170 if (NS_FAILED(result)) |
|
171 return result; |
|
172 } |
|
173 |
|
174 return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT); |
|
175 } |
|
176 |
|
177 nsresult Stop() |
|
178 { |
|
179 if (mTimer) |
|
180 { |
|
181 mTimer->Cancel(); |
|
182 mTimer = 0; |
|
183 } |
|
184 |
|
185 mContent = nullptr; |
|
186 return NS_OK; |
|
187 } |
|
188 |
|
189 nsresult Init(nsFrameSelection* aFrameSelection, Selection* aSelection) |
|
190 { |
|
191 mFrameSelection = aFrameSelection; |
|
192 mSelection = aSelection; |
|
193 return NS_OK; |
|
194 } |
|
195 |
|
196 nsresult SetDelay(uint32_t aDelay) |
|
197 { |
|
198 mDelay = aDelay; |
|
199 return NS_OK; |
|
200 } |
|
201 |
|
202 NS_IMETHOD Notify(nsITimer *timer) MOZ_OVERRIDE |
|
203 { |
|
204 if (mSelection && mPresContext) |
|
205 { |
|
206 nsWeakFrame frame = |
|
207 mContent ? mPresContext->GetPrimaryFrameFor(mContent) : nullptr; |
|
208 if (!frame) |
|
209 return NS_OK; |
|
210 mContent = nullptr; |
|
211 |
|
212 nsPoint pt = mPoint - |
|
213 frame->GetOffsetTo(mPresContext->PresShell()->FrameManager()->GetRootFrame()); |
|
214 mFrameSelection->HandleDrag(frame, pt); |
|
215 if (!frame.IsAlive()) |
|
216 return NS_OK; |
|
217 |
|
218 NS_ASSERTION(frame->PresContext() == mPresContext, "document mismatch?"); |
|
219 mSelection->DoAutoScroll(frame, pt); |
|
220 } |
|
221 return NS_OK; |
|
222 } |
|
223 private: |
|
224 nsFrameSelection *mFrameSelection; |
|
225 Selection* mSelection; |
|
226 nsPresContext *mPresContext; |
|
227 // relative to mPresContext's root frame |
|
228 nsPoint mPoint; |
|
229 nsCOMPtr<nsITimer> mTimer; |
|
230 nsCOMPtr<nsIContent> mContent; |
|
231 uint32_t mDelay; |
|
232 }; |
|
233 |
|
234 NS_IMPL_ISUPPORTS(nsAutoScrollTimer, nsITimerCallback) |
|
235 |
|
236 nsresult NS_NewDomSelection(nsISelection **aDomSelection) |
|
237 { |
|
238 Selection* rlist = new Selection; |
|
239 *aDomSelection = (nsISelection *)rlist; |
|
240 NS_ADDREF(rlist); |
|
241 return NS_OK; |
|
242 } |
|
243 |
|
244 static int8_t |
|
245 GetIndexFromSelectionType(SelectionType aType) |
|
246 { |
|
247 switch (aType) |
|
248 { |
|
249 case nsISelectionController::SELECTION_NORMAL: return 0; break; |
|
250 case nsISelectionController::SELECTION_SPELLCHECK: return 1; break; |
|
251 case nsISelectionController::SELECTION_IME_RAWINPUT: return 2; break; |
|
252 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: return 3; break; |
|
253 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: return 4; break; |
|
254 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: return 5; break; |
|
255 case nsISelectionController::SELECTION_ACCESSIBILITY: return 6; break; |
|
256 case nsISelectionController::SELECTION_FIND: return 7; break; |
|
257 case nsISelectionController::SELECTION_URLSECONDARY: return 8; break; |
|
258 default: |
|
259 return -1; break; |
|
260 } |
|
261 /* NOTREACHED */ |
|
262 return 0; |
|
263 } |
|
264 |
|
265 static SelectionType |
|
266 GetSelectionTypeFromIndex(int8_t aIndex) |
|
267 { |
|
268 switch (aIndex) |
|
269 { |
|
270 case 0: return nsISelectionController::SELECTION_NORMAL; break; |
|
271 case 1: return nsISelectionController::SELECTION_SPELLCHECK; break; |
|
272 case 2: return nsISelectionController::SELECTION_IME_RAWINPUT; break; |
|
273 case 3: return nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT; break; |
|
274 case 4: return nsISelectionController::SELECTION_IME_CONVERTEDTEXT; break; |
|
275 case 5: return nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT; break; |
|
276 case 6: return nsISelectionController::SELECTION_ACCESSIBILITY; break; |
|
277 case 7: return nsISelectionController::SELECTION_FIND; break; |
|
278 case 8: return nsISelectionController::SELECTION_URLSECONDARY; break; |
|
279 default: |
|
280 return nsISelectionController::SELECTION_NORMAL; break; |
|
281 } |
|
282 /* NOTREACHED */ |
|
283 return 0; |
|
284 } |
|
285 |
|
286 /* |
|
287 The limiter is used specifically for the text areas and textfields |
|
288 In that case it is the DIV tag that is anonymously created for the text |
|
289 areas/fields. Text nodes and BR nodes fall beneath it. In the case of a |
|
290 BR node the limiter will be the parent and the offset will point before or |
|
291 after the BR node. In the case of the text node the parent content is |
|
292 the text node itself and the offset will be the exact character position. |
|
293 The offset is not important to check for validity. Simply look at the |
|
294 passed in content. If it equals the limiter then the selection point is valid. |
|
295 If its parent it the limiter then the point is also valid. In the case of |
|
296 NO limiter all points are valid since you are in a topmost iframe. (browser |
|
297 or composer) |
|
298 */ |
|
299 bool |
|
300 IsValidSelectionPoint(nsFrameSelection *aFrameSel, nsINode *aNode) |
|
301 { |
|
302 if (!aFrameSel || !aNode) |
|
303 return false; |
|
304 |
|
305 nsIContent *limiter = aFrameSel->GetLimiter(); |
|
306 if (limiter && limiter != aNode && limiter != aNode->GetParent()) { |
|
307 //if newfocus == the limiter. that's ok. but if not there and not parent bad |
|
308 return false; //not in the right content. tLimiter said so |
|
309 } |
|
310 |
|
311 limiter = aFrameSel->GetAncestorLimiter(); |
|
312 return !limiter || nsContentUtils::ContentIsDescendantOf(aNode, limiter); |
|
313 } |
|
314 |
|
315 |
|
316 ////////////BEGIN nsFrameSelection methods |
|
317 |
|
318 nsFrameSelection::nsFrameSelection() |
|
319 { |
|
320 int32_t i; |
|
321 for (i = 0;i<nsISelectionController::NUM_SELECTIONTYPES;i++){ |
|
322 mDomSelections[i] = new Selection(this); |
|
323 mDomSelections[i]->SetType(GetSelectionTypeFromIndex(i)); |
|
324 } |
|
325 mBatching = 0; |
|
326 mChangesDuringBatching = false; |
|
327 mNotifyFrames = true; |
|
328 |
|
329 mMouseDoubleDownState = false; |
|
330 |
|
331 mHint = HINTLEFT; |
|
332 mCaretBidiLevel = BIDI_LEVEL_UNDEFINED; |
|
333 mDragSelectingCells = false; |
|
334 mSelectingTableCellMode = 0; |
|
335 mSelectedCellIndex = 0; |
|
336 |
|
337 // Check to see if the autocopy pref is enabled |
|
338 // and add the autocopy listener if it is |
|
339 if (Preferences::GetBool("clipboard.autocopy")) { |
|
340 nsAutoCopyListener *autoCopy = nsAutoCopyListener::GetInstance(); |
|
341 |
|
342 if (autoCopy) { |
|
343 int8_t index = |
|
344 GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
345 if (mDomSelections[index]) { |
|
346 autoCopy->Listen(mDomSelections[index]); |
|
347 } |
|
348 } |
|
349 } |
|
350 |
|
351 mDisplaySelection = nsISelectionController::SELECTION_OFF; |
|
352 mSelectionChangeReason = nsISelectionListener::NO_REASON; |
|
353 |
|
354 mDelayedMouseEventValid = false; |
|
355 // These values are not used since they are only valid when |
|
356 // mDelayedMouseEventValid is true, and setting mDelayedMouseEventValid |
|
357 //alwaysoverrides these values. |
|
358 mDelayedMouseEventIsShift = false; |
|
359 mDelayedMouseEventClickCount = 0; |
|
360 } |
|
361 |
|
362 |
|
363 NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameSelection) |
|
364 |
|
365 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameSelection) |
|
366 int32_t i; |
|
367 for (i = 0; i < nsISelectionController::NUM_SELECTIONTYPES; ++i) { |
|
368 tmp->mDomSelections[i] = nullptr; |
|
369 } |
|
370 |
|
371 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCellParent) |
|
372 tmp->mSelectingTableCellMode = 0; |
|
373 tmp->mDragSelectingCells = false; |
|
374 NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartSelectedCell) |
|
375 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndSelectedCell) |
|
376 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAppendStartSelectedCell) |
|
377 NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnselectCellOnMouseUp) |
|
378 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMaintainRange) |
|
379 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiter) |
|
380 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAncestorLimiter) |
|
381 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
382 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameSelection) |
|
383 if (tmp->mShell && tmp->mShell->GetDocument() && |
|
384 nsCCUncollectableMarker::InGeneration(cb, |
|
385 tmp->mShell->GetDocument()-> |
|
386 GetMarkedCCGeneration())) { |
|
387 return NS_SUCCESS_INTERRUPTED_TRAVERSE; |
|
388 } |
|
389 int32_t i; |
|
390 for (i = 0; i < nsISelectionController::NUM_SELECTIONTYPES; ++i) { |
|
391 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDomSelections[i]) |
|
392 } |
|
393 |
|
394 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCellParent) |
|
395 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStartSelectedCell) |
|
396 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndSelectedCell) |
|
397 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAppendStartSelectedCell) |
|
398 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnselectCellOnMouseUp) |
|
399 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMaintainRange) |
|
400 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiter) |
|
401 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAncestorLimiter) |
|
402 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
403 |
|
404 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsFrameSelection, AddRef) |
|
405 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsFrameSelection, Release) |
|
406 |
|
407 |
|
408 nsresult |
|
409 nsFrameSelection::FetchDesiredX(nscoord &aDesiredX) //the x position requested by the Key Handling for up down |
|
410 { |
|
411 if (!mShell) |
|
412 { |
|
413 NS_ERROR("fetch desired X failed"); |
|
414 return NS_ERROR_FAILURE; |
|
415 } |
|
416 if (mDesiredXSet) |
|
417 { |
|
418 aDesiredX = mDesiredX; |
|
419 return NS_OK; |
|
420 } |
|
421 |
|
422 nsRefPtr<nsCaret> caret = mShell->GetCaret(); |
|
423 if (!caret) |
|
424 return NS_ERROR_NULL_POINTER; |
|
425 |
|
426 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
427 nsresult result = caret->SetCaretDOMSelection(mDomSelections[index]); |
|
428 if (NS_FAILED(result)) |
|
429 return result; |
|
430 |
|
431 nsRect coord; |
|
432 nsIFrame* caretFrame = caret->GetGeometry(mDomSelections[index], &coord); |
|
433 if (!caretFrame) |
|
434 return NS_ERROR_FAILURE; |
|
435 nsPoint viewOffset(0, 0); |
|
436 nsView* view = nullptr; |
|
437 caretFrame->GetOffsetFromView(viewOffset, &view); |
|
438 if (view) |
|
439 coord.x += viewOffset.x; |
|
440 |
|
441 aDesiredX = coord.x; |
|
442 return NS_OK; |
|
443 } |
|
444 |
|
445 |
|
446 |
|
447 void |
|
448 nsFrameSelection::InvalidateDesiredX() //do not listen to mDesiredX you must get another. |
|
449 { |
|
450 mDesiredXSet = false; |
|
451 } |
|
452 |
|
453 |
|
454 |
|
455 void |
|
456 nsFrameSelection::SetDesiredX(nscoord aX) //set the mDesiredX |
|
457 { |
|
458 mDesiredX = aX; |
|
459 mDesiredXSet = true; |
|
460 } |
|
461 |
|
462 nsresult |
|
463 nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(nsIFrame *aFrame, |
|
464 nsPoint& aPoint, |
|
465 nsIFrame **aRetFrame, |
|
466 nsPoint& aRetPoint) |
|
467 { |
|
468 // |
|
469 // The whole point of this method is to return a frame and point that |
|
470 // that lie within the same valid subtree as the anchor node's frame, |
|
471 // for use with the method GetContentAndOffsetsFromPoint(). |
|
472 // |
|
473 // A valid subtree is defined to be one where all the content nodes in |
|
474 // the tree have a valid parent-child relationship. |
|
475 // |
|
476 // If the anchor frame and aFrame are in the same subtree, aFrame will |
|
477 // be returned in aRetFrame. If they are in different subtrees, we |
|
478 // return the frame for the root of the subtree. |
|
479 // |
|
480 |
|
481 if (!aFrame || !aRetFrame) |
|
482 return NS_ERROR_NULL_POINTER; |
|
483 |
|
484 *aRetFrame = aFrame; |
|
485 aRetPoint = aPoint; |
|
486 |
|
487 // |
|
488 // Get the frame and content for the selection's anchor point! |
|
489 // |
|
490 |
|
491 nsresult result; |
|
492 nsCOMPtr<nsIDOMNode> anchorNode; |
|
493 int32_t anchorOffset = 0; |
|
494 |
|
495 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
496 if (!mDomSelections[index]) |
|
497 return NS_ERROR_NULL_POINTER; |
|
498 |
|
499 result = mDomSelections[index]->GetAnchorNode(getter_AddRefs(anchorNode)); |
|
500 |
|
501 if (NS_FAILED(result)) |
|
502 return result; |
|
503 |
|
504 if (!anchorNode) |
|
505 return NS_OK; |
|
506 |
|
507 result = mDomSelections[index]->GetAnchorOffset(&anchorOffset); |
|
508 |
|
509 if (NS_FAILED(result)) |
|
510 return result; |
|
511 |
|
512 nsCOMPtr<nsIContent> anchorContent = do_QueryInterface(anchorNode); |
|
513 |
|
514 if (!anchorContent) |
|
515 return NS_ERROR_FAILURE; |
|
516 |
|
517 // |
|
518 // Now find the root of the subtree containing the anchor's content. |
|
519 // |
|
520 |
|
521 NS_ENSURE_STATE(mShell); |
|
522 nsIContent* anchorRoot = anchorContent->GetSelectionRootContent(mShell); |
|
523 NS_ENSURE_TRUE(anchorRoot, NS_ERROR_UNEXPECTED); |
|
524 |
|
525 // |
|
526 // Now find the root of the subtree containing aFrame's content. |
|
527 // |
|
528 |
|
529 nsIContent* content = aFrame->GetContent(); |
|
530 |
|
531 if (content) |
|
532 { |
|
533 nsIContent* contentRoot = content->GetSelectionRootContent(mShell); |
|
534 NS_ENSURE_TRUE(contentRoot, NS_ERROR_UNEXPECTED); |
|
535 |
|
536 if (anchorRoot == contentRoot) |
|
537 { |
|
538 // If the aFrame's content isn't the capturing content, it should be |
|
539 // a descendant. At this time, we can return simply. |
|
540 nsIContent* capturedContent = nsIPresShell::GetCapturingContent(); |
|
541 if (capturedContent != content) |
|
542 { |
|
543 return NS_OK; |
|
544 } |
|
545 |
|
546 // Find the frame under the mouse cursor with the root frame. |
|
547 // At this time, don't use the anchor's frame because it may not have |
|
548 // fixed positioned frames. |
|
549 nsIFrame* rootFrame = mShell->FrameManager()->GetRootFrame(); |
|
550 nsPoint ptInRoot = aPoint + aFrame->GetOffsetTo(rootFrame); |
|
551 nsIFrame* cursorFrame = |
|
552 nsLayoutUtils::GetFrameForPoint(rootFrame, ptInRoot); |
|
553 |
|
554 // If the mouse cursor in on a frame which is descendant of same |
|
555 // selection root, we can expand the selection to the frame. |
|
556 if (cursorFrame && cursorFrame->PresContext()->PresShell() == mShell) |
|
557 { |
|
558 nsIContent* cursorContent = cursorFrame->GetContent(); |
|
559 NS_ENSURE_TRUE(cursorContent, NS_ERROR_FAILURE); |
|
560 nsIContent* cursorContentRoot = |
|
561 cursorContent->GetSelectionRootContent(mShell); |
|
562 NS_ENSURE_TRUE(cursorContentRoot, NS_ERROR_UNEXPECTED); |
|
563 if (cursorContentRoot == anchorRoot) |
|
564 { |
|
565 *aRetFrame = cursorFrame; |
|
566 aRetPoint = aPoint + aFrame->GetOffsetTo(cursorFrame); |
|
567 return NS_OK; |
|
568 } |
|
569 } |
|
570 // Otherwise, e.g., the cursor isn't on any frames (e.g., the mouse |
|
571 // cursor is out of the window), we should use the frame of the anchor |
|
572 // root. |
|
573 } |
|
574 } |
|
575 |
|
576 // |
|
577 // When we can't find a frame which is under the mouse cursor and has a same |
|
578 // selection root as the anchor node's, we should return the selection root |
|
579 // frame. |
|
580 // |
|
581 |
|
582 *aRetFrame = anchorRoot->GetPrimaryFrame(); |
|
583 |
|
584 if (!*aRetFrame) |
|
585 return NS_ERROR_FAILURE; |
|
586 |
|
587 // |
|
588 // Now make sure that aRetPoint is converted to the same coordinate |
|
589 // system used by aRetFrame. |
|
590 // |
|
591 |
|
592 aRetPoint = aPoint + aFrame->GetOffsetTo(*aRetFrame); |
|
593 |
|
594 return NS_OK; |
|
595 } |
|
596 |
|
597 void |
|
598 nsFrameSelection::SetCaretBidiLevel(uint8_t aLevel) |
|
599 { |
|
600 // If the current level is undefined, we have just inserted new text. |
|
601 // In this case, we don't want to reset the keyboard language |
|
602 mCaretBidiLevel = aLevel; |
|
603 return; |
|
604 } |
|
605 |
|
606 uint8_t |
|
607 nsFrameSelection::GetCaretBidiLevel() const |
|
608 { |
|
609 return mCaretBidiLevel; |
|
610 } |
|
611 |
|
612 void |
|
613 nsFrameSelection::UndefineCaretBidiLevel() |
|
614 { |
|
615 mCaretBidiLevel |= BIDI_LEVEL_UNDEFINED; |
|
616 } |
|
617 |
|
618 #ifdef PRINT_RANGE |
|
619 void printRange(nsRange *aDomRange) |
|
620 { |
|
621 if (!aDomRange) |
|
622 { |
|
623 printf("NULL nsIDOMRange\n"); |
|
624 } |
|
625 nsINode* startNode = aDomRange->GetStartParent(); |
|
626 nsINode* endNode = aDomRange->GetEndParent(); |
|
627 int32_t startOffset = aDomRange->StartOffset(); |
|
628 int32_t endOffset = aDomRange->EndOffset(); |
|
629 |
|
630 printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n", |
|
631 (unsigned long)aDomRange, |
|
632 (unsigned long)startNode, (long)startOffset, |
|
633 (unsigned long)endNode, (long)endOffset); |
|
634 |
|
635 } |
|
636 #endif /* PRINT_RANGE */ |
|
637 |
|
638 static |
|
639 nsIAtom *GetTag(nsINode *aNode) |
|
640 { |
|
641 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); |
|
642 if (!content) |
|
643 { |
|
644 NS_NOTREACHED("bad node passed to GetTag()"); |
|
645 return nullptr; |
|
646 } |
|
647 |
|
648 return content->Tag(); |
|
649 } |
|
650 |
|
651 // Returns the parent |
|
652 nsINode* |
|
653 ParentOffset(nsINode *aNode, int32_t *aChildOffset) |
|
654 { |
|
655 if (!aNode || !aChildOffset) |
|
656 return nullptr; |
|
657 |
|
658 nsIContent* parent = aNode->GetParent(); |
|
659 if (parent) |
|
660 { |
|
661 *aChildOffset = parent->IndexOf(aNode); |
|
662 |
|
663 return parent; |
|
664 } |
|
665 |
|
666 return nullptr; |
|
667 } |
|
668 |
|
669 static nsINode* |
|
670 GetCellParent(nsINode *aDomNode) |
|
671 { |
|
672 if (!aDomNode) |
|
673 return nullptr; |
|
674 nsINode* current = aDomNode; |
|
675 // Start with current node and look for a table cell |
|
676 while (current) |
|
677 { |
|
678 nsIAtom* tag = GetTag(current); |
|
679 if (tag == nsGkAtoms::td || tag == nsGkAtoms::th) |
|
680 return current; |
|
681 current = current->GetParent(); |
|
682 } |
|
683 return nullptr; |
|
684 } |
|
685 |
|
686 |
|
687 void |
|
688 nsFrameSelection::Init(nsIPresShell *aShell, nsIContent *aLimiter) |
|
689 { |
|
690 mShell = aShell; |
|
691 mMouseDownState = false; |
|
692 mDesiredXSet = false; |
|
693 mLimiter = aLimiter; |
|
694 mCaretMovementStyle = |
|
695 Preferences::GetInt("bidi.edit.caret_movement_style", 2); |
|
696 } |
|
697 |
|
698 nsresult |
|
699 nsFrameSelection::MoveCaret(uint32_t aKeycode, |
|
700 bool aContinueSelection, |
|
701 nsSelectionAmount aAmount) |
|
702 { |
|
703 bool visualMovement = |
|
704 (aKeycode == nsIDOMKeyEvent::DOM_VK_BACK_SPACE || |
|
705 aKeycode == nsIDOMKeyEvent::DOM_VK_DELETE || |
|
706 aKeycode == nsIDOMKeyEvent::DOM_VK_HOME || |
|
707 aKeycode == nsIDOMKeyEvent::DOM_VK_END) ? |
|
708 false : // Delete operations and home/end are always logical |
|
709 mCaretMovementStyle == 1 || |
|
710 (mCaretMovementStyle == 2 && !aContinueSelection); |
|
711 |
|
712 return MoveCaret(aKeycode, aContinueSelection, aAmount, visualMovement); |
|
713 } |
|
714 |
|
715 nsresult |
|
716 nsFrameSelection::MoveCaret(uint32_t aKeycode, |
|
717 bool aContinueSelection, |
|
718 nsSelectionAmount aAmount, |
|
719 bool aVisualMovement) |
|
720 { |
|
721 NS_ENSURE_STATE(mShell); |
|
722 // Flush out layout, since we need it to be up to date to do caret |
|
723 // positioning. |
|
724 mShell->FlushPendingNotifications(Flush_Layout); |
|
725 |
|
726 if (!mShell) { |
|
727 return NS_OK; |
|
728 } |
|
729 |
|
730 nsPresContext *context = mShell->GetPresContext(); |
|
731 if (!context) |
|
732 return NS_ERROR_FAILURE; |
|
733 |
|
734 bool isCollapsed; |
|
735 nscoord desiredX = 0; //we must keep this around and revalidate it when its just UP/DOWN |
|
736 |
|
737 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
738 nsRefPtr<Selection> sel = mDomSelections[index]; |
|
739 if (!sel) |
|
740 return NS_ERROR_NULL_POINTER; |
|
741 |
|
742 int32_t scrollFlags = 0; |
|
743 nsINode* focusNode = sel->GetFocusNode(); |
|
744 if (focusNode && |
|
745 (focusNode->IsEditable() || |
|
746 (focusNode->IsElement() && |
|
747 focusNode->AsElement()->State(). |
|
748 HasState(NS_EVENT_STATE_MOZ_READWRITE)))) { |
|
749 // If caret moves in editor, it should cause scrolling even if it's in |
|
750 // overflow: hidden;. |
|
751 scrollFlags |= Selection::SCROLL_OVERFLOW_HIDDEN; |
|
752 } |
|
753 |
|
754 nsresult result = sel->GetIsCollapsed(&isCollapsed); |
|
755 if (NS_FAILED(result)) |
|
756 return result; |
|
757 if (aKeycode == nsIDOMKeyEvent::DOM_VK_UP || |
|
758 aKeycode == nsIDOMKeyEvent::DOM_VK_DOWN) |
|
759 { |
|
760 result = FetchDesiredX(desiredX); |
|
761 if (NS_FAILED(result)) |
|
762 return result; |
|
763 SetDesiredX(desiredX); |
|
764 } |
|
765 |
|
766 int32_t caretStyle = Preferences::GetInt("layout.selection.caret_style", 0); |
|
767 if (caretStyle == 0 |
|
768 #ifdef XP_WIN |
|
769 && aKeycode != nsIDOMKeyEvent::DOM_VK_UP |
|
770 && aKeycode != nsIDOMKeyEvent::DOM_VK_DOWN |
|
771 #endif |
|
772 ) { |
|
773 // Put caret at the selection edge in the |aKeycode| direction. |
|
774 caretStyle = 2; |
|
775 } |
|
776 |
|
777 if (!isCollapsed && !aContinueSelection && caretStyle == 2) { |
|
778 switch (aKeycode){ |
|
779 case nsIDOMKeyEvent::DOM_VK_LEFT : |
|
780 case nsIDOMKeyEvent::DOM_VK_UP : |
|
781 { |
|
782 const nsRange* anchorFocusRange = sel->GetAnchorFocusRange(); |
|
783 if (anchorFocusRange) { |
|
784 PostReason(nsISelectionListener::COLLAPSETOSTART_REASON); |
|
785 sel->Collapse(anchorFocusRange->GetStartParent(), |
|
786 anchorFocusRange->StartOffset()); |
|
787 } |
|
788 mHint = HINTRIGHT; |
|
789 sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION, |
|
790 nsIPresShell::ScrollAxis(), |
|
791 nsIPresShell::ScrollAxis(), scrollFlags); |
|
792 return NS_OK; |
|
793 } |
|
794 |
|
795 case nsIDOMKeyEvent::DOM_VK_RIGHT : |
|
796 case nsIDOMKeyEvent::DOM_VK_DOWN : |
|
797 { |
|
798 const nsRange* anchorFocusRange = sel->GetAnchorFocusRange(); |
|
799 if (anchorFocusRange) { |
|
800 PostReason(nsISelectionListener::COLLAPSETOEND_REASON); |
|
801 sel->Collapse(anchorFocusRange->GetEndParent(), |
|
802 anchorFocusRange->EndOffset()); |
|
803 } |
|
804 mHint = HINTLEFT; |
|
805 sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION, |
|
806 nsIPresShell::ScrollAxis(), |
|
807 nsIPresShell::ScrollAxis(), scrollFlags); |
|
808 return NS_OK; |
|
809 } |
|
810 } |
|
811 } |
|
812 |
|
813 nsIFrame *frame; |
|
814 int32_t offsetused = 0; |
|
815 result = sel->GetPrimaryFrameForFocusNode(&frame, &offsetused, |
|
816 aVisualMovement); |
|
817 |
|
818 if (NS_FAILED(result) || !frame) |
|
819 return NS_FAILED(result) ? result : NS_ERROR_FAILURE; |
|
820 |
|
821 //set data using mLimiter to stop on scroll views. If we have a limiter then we stop peeking |
|
822 //when we hit scrollable views. If no limiter then just let it go ahead |
|
823 nsPeekOffsetStruct pos(aAmount, eDirPrevious, offsetused, desiredX, |
|
824 true, mLimiter != nullptr, true, aVisualMovement); |
|
825 |
|
826 nsBidiLevel baseLevel = nsBidiPresUtils::GetFrameBaseLevel(frame); |
|
827 |
|
828 HINT tHint(mHint); //temporary variable so we dont set mHint until it is necessary |
|
829 switch (aKeycode){ |
|
830 case nsIDOMKeyEvent::DOM_VK_RIGHT : |
|
831 InvalidateDesiredX(); |
|
832 pos.mDirection = (baseLevel & 1) ? eDirPrevious : eDirNext; |
|
833 break; |
|
834 case nsIDOMKeyEvent::DOM_VK_LEFT : |
|
835 InvalidateDesiredX(); |
|
836 pos.mDirection = (baseLevel & 1) ? eDirNext : eDirPrevious; |
|
837 break; |
|
838 case nsIDOMKeyEvent::DOM_VK_DELETE : |
|
839 InvalidateDesiredX(); |
|
840 pos.mDirection = eDirNext; |
|
841 break; |
|
842 case nsIDOMKeyEvent::DOM_VK_BACK_SPACE : |
|
843 InvalidateDesiredX(); |
|
844 pos.mDirection = eDirPrevious; |
|
845 break; |
|
846 case nsIDOMKeyEvent::DOM_VK_DOWN : |
|
847 pos.mAmount = eSelectLine; |
|
848 pos.mDirection = eDirNext; |
|
849 break; |
|
850 case nsIDOMKeyEvent::DOM_VK_UP : |
|
851 pos.mAmount = eSelectLine; |
|
852 pos.mDirection = eDirPrevious; |
|
853 break; |
|
854 case nsIDOMKeyEvent::DOM_VK_HOME : |
|
855 InvalidateDesiredX(); |
|
856 pos.mAmount = eSelectBeginLine; |
|
857 break; |
|
858 case nsIDOMKeyEvent::DOM_VK_END : |
|
859 InvalidateDesiredX(); |
|
860 pos.mAmount = eSelectEndLine; |
|
861 break; |
|
862 default :return NS_ERROR_FAILURE; |
|
863 } |
|
864 PostReason(nsISelectionListener::KEYPRESS_REASON); |
|
865 if (NS_SUCCEEDED(result = frame->PeekOffset(&pos)) && pos.mResultContent) |
|
866 { |
|
867 nsIFrame *theFrame; |
|
868 int32_t currentOffset, frameStart, frameEnd; |
|
869 |
|
870 if (aAmount >= eSelectCharacter && aAmount <= eSelectWord) |
|
871 { |
|
872 // For left/right, PeekOffset() sets pos.mResultFrame correctly, but does not set pos.mAttachForward, |
|
873 // so determine the hint here based on the result frame and offset: |
|
874 // If we're at the end of a text frame, set the hint to HINTLEFT to indicate that we |
|
875 // want the caret displayed at the end of this frame, not at the beginning of the next one. |
|
876 theFrame = pos.mResultFrame; |
|
877 theFrame->GetOffsets(frameStart, frameEnd); |
|
878 currentOffset = pos.mContentOffset; |
|
879 if (frameEnd == currentOffset && !(frameStart == 0 && frameEnd == 0)) |
|
880 tHint = HINTLEFT; |
|
881 else |
|
882 tHint = HINTRIGHT; |
|
883 } else { |
|
884 // For up/down and home/end, pos.mResultFrame might not be set correctly, or not at all. |
|
885 // In these cases, get the frame based on the content and hint returned by PeekOffset(). |
|
886 tHint = (HINT)pos.mAttachForward; |
|
887 theFrame = GetFrameForNodeOffset(pos.mResultContent, pos.mContentOffset, |
|
888 tHint, ¤tOffset); |
|
889 if (!theFrame) |
|
890 return NS_ERROR_FAILURE; |
|
891 |
|
892 theFrame->GetOffsets(frameStart, frameEnd); |
|
893 } |
|
894 |
|
895 if (context->BidiEnabled()) |
|
896 { |
|
897 switch (aKeycode) { |
|
898 case nsIDOMKeyEvent::DOM_VK_HOME: |
|
899 case nsIDOMKeyEvent::DOM_VK_END: |
|
900 // set the caret Bidi level to the paragraph embedding level |
|
901 SetCaretBidiLevel(NS_GET_BASE_LEVEL(theFrame)); |
|
902 break; |
|
903 |
|
904 default: |
|
905 // If the current position is not a frame boundary, it's enough just to take the Bidi level of the current frame |
|
906 if ((pos.mContentOffset != frameStart && pos.mContentOffset != frameEnd) |
|
907 || (eSelectLine == aAmount)) |
|
908 { |
|
909 SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(theFrame)); |
|
910 } |
|
911 else |
|
912 BidiLevelFromMove(mShell, pos.mResultContent, pos.mContentOffset, aKeycode, tHint); |
|
913 } |
|
914 } |
|
915 result = TakeFocus(pos.mResultContent, pos.mContentOffset, pos.mContentOffset, |
|
916 tHint, aContinueSelection, false); |
|
917 } else if (aKeycode == nsIDOMKeyEvent::DOM_VK_RIGHT && !aContinueSelection) { |
|
918 // Collapse selection if PeekOffset failed, we either |
|
919 // 1. bumped into the BRFrame, bug 207623 |
|
920 // 2. had select-all in a text input (DIV range), bug 352759. |
|
921 bool isBRFrame = frame->GetType() == nsGkAtoms::brFrame; |
|
922 sel->Collapse(sel->GetFocusNode(), sel->FocusOffset()); |
|
923 // Note: 'frame' might be dead here. |
|
924 if (!isBRFrame) { |
|
925 mHint = HINTLEFT; // We're now at the end of the frame to the left. |
|
926 } |
|
927 result = NS_OK; |
|
928 } |
|
929 if (NS_SUCCEEDED(result)) |
|
930 { |
|
931 result = mDomSelections[index]-> |
|
932 ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION, |
|
933 nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(), |
|
934 scrollFlags); |
|
935 } |
|
936 |
|
937 return result; |
|
938 } |
|
939 |
|
940 //END nsFrameSelection methods |
|
941 |
|
942 |
|
943 //BEGIN nsFrameSelection methods |
|
944 |
|
945 NS_IMETHODIMP |
|
946 Selection::ToString(nsAString& aReturn) |
|
947 { |
|
948 // We need Flush_Style here to make sure frames have been created for |
|
949 // the selected content. Use mFrameSelection->GetShell() which returns |
|
950 // null if the Selection has been disconnected (the shell is Destroyed). |
|
951 nsCOMPtr<nsIPresShell> shell = |
|
952 mFrameSelection ? mFrameSelection->GetShell() : nullptr; |
|
953 if (!shell) { |
|
954 aReturn.Truncate(); |
|
955 return NS_OK; |
|
956 } |
|
957 shell->FlushPendingNotifications(Flush_Style); |
|
958 |
|
959 return ToStringWithFormat("text/plain", |
|
960 nsIDocumentEncoder::SkipInvisibleContent, |
|
961 0, aReturn); |
|
962 } |
|
963 |
|
964 void |
|
965 Selection::Stringify(nsAString& aResult) |
|
966 { |
|
967 // Eat the error code |
|
968 ToString(aResult); |
|
969 } |
|
970 |
|
971 NS_IMETHODIMP |
|
972 Selection::ToStringWithFormat(const char* aFormatType, uint32_t aFlags, |
|
973 int32_t aWrapCol, nsAString& aReturn) |
|
974 { |
|
975 ErrorResult result; |
|
976 NS_ConvertUTF8toUTF16 format(aFormatType); |
|
977 ToStringWithFormat(format, aFlags, aWrapCol, aReturn, result); |
|
978 if (result.Failed()) { |
|
979 return result.ErrorCode(); |
|
980 } |
|
981 return NS_OK; |
|
982 } |
|
983 |
|
984 void |
|
985 Selection::ToStringWithFormat(const nsAString& aFormatType, uint32_t aFlags, |
|
986 int32_t aWrapCol, nsAString& aReturn, |
|
987 ErrorResult& aRv) |
|
988 { |
|
989 nsresult rv = NS_OK; |
|
990 NS_ConvertUTF8toUTF16 formatType( NS_DOC_ENCODER_CONTRACTID_BASE ); |
|
991 formatType.Append(aFormatType); |
|
992 nsCOMPtr<nsIDocumentEncoder> encoder = |
|
993 do_CreateInstance(NS_ConvertUTF16toUTF8(formatType).get(), &rv); |
|
994 if (NS_FAILED(rv)) { |
|
995 aRv.Throw(rv); |
|
996 return; |
|
997 } |
|
998 |
|
999 nsIPresShell* shell = GetPresShell(); |
|
1000 if (!shell) { |
|
1001 aRv.Throw(NS_ERROR_FAILURE); |
|
1002 return; |
|
1003 } |
|
1004 |
|
1005 nsIDocument *doc = shell->GetDocument(); |
|
1006 |
|
1007 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc); |
|
1008 NS_ASSERTION(domDoc, "Need a document"); |
|
1009 |
|
1010 // Flags should always include OutputSelectionOnly if we're coming from here: |
|
1011 aFlags |= nsIDocumentEncoder::OutputSelectionOnly; |
|
1012 nsAutoString readstring; |
|
1013 readstring.Assign(aFormatType); |
|
1014 rv = encoder->Init(domDoc, readstring, aFlags); |
|
1015 if (NS_FAILED(rv)) { |
|
1016 aRv.Throw(rv); |
|
1017 return; |
|
1018 } |
|
1019 |
|
1020 encoder->SetSelection(this); |
|
1021 if (aWrapCol != 0) |
|
1022 encoder->SetWrapColumn(aWrapCol); |
|
1023 |
|
1024 rv = encoder->EncodeToString(aReturn); |
|
1025 if (NS_FAILED(rv)) { |
|
1026 aRv.Throw(rv); |
|
1027 } |
|
1028 } |
|
1029 |
|
1030 NS_IMETHODIMP |
|
1031 Selection::SetInterlinePosition(bool aHintRight) |
|
1032 { |
|
1033 ErrorResult result; |
|
1034 SetInterlinePosition(aHintRight, result); |
|
1035 if (result.Failed()) { |
|
1036 return result.ErrorCode(); |
|
1037 } |
|
1038 return NS_OK; |
|
1039 } |
|
1040 |
|
1041 void |
|
1042 Selection::SetInterlinePosition(bool aHintRight, ErrorResult& aRv) |
|
1043 { |
|
1044 if (!mFrameSelection) { |
|
1045 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection |
|
1046 return; |
|
1047 } |
|
1048 nsFrameSelection::HINT hint; |
|
1049 if (aHintRight) |
|
1050 hint = nsFrameSelection::HINTRIGHT; |
|
1051 else |
|
1052 hint = nsFrameSelection::HINTLEFT; |
|
1053 mFrameSelection->SetHint(hint); |
|
1054 } |
|
1055 |
|
1056 NS_IMETHODIMP |
|
1057 Selection::GetInterlinePosition(bool* aHintRight) |
|
1058 { |
|
1059 ErrorResult result; |
|
1060 *aHintRight = GetInterlinePosition(result); |
|
1061 if (result.Failed()) { |
|
1062 return result.ErrorCode(); |
|
1063 } |
|
1064 return NS_OK; |
|
1065 } |
|
1066 |
|
1067 bool |
|
1068 Selection::GetInterlinePosition(ErrorResult& aRv) |
|
1069 { |
|
1070 if (!mFrameSelection) { |
|
1071 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection |
|
1072 return false; |
|
1073 } |
|
1074 return (mFrameSelection->GetHint() == nsFrameSelection::HINTRIGHT); |
|
1075 } |
|
1076 |
|
1077 nsPrevNextBidiLevels |
|
1078 nsFrameSelection::GetPrevNextBidiLevels(nsIContent *aNode, |
|
1079 uint32_t aContentOffset, |
|
1080 bool aJumpLines) const |
|
1081 { |
|
1082 return GetPrevNextBidiLevels(aNode, aContentOffset, mHint, aJumpLines); |
|
1083 } |
|
1084 |
|
1085 nsPrevNextBidiLevels |
|
1086 nsFrameSelection::GetPrevNextBidiLevels(nsIContent *aNode, |
|
1087 uint32_t aContentOffset, |
|
1088 HINT aHint, |
|
1089 bool aJumpLines) const |
|
1090 { |
|
1091 // Get the level of the frames on each side |
|
1092 nsIFrame *currentFrame; |
|
1093 int32_t currentOffset; |
|
1094 int32_t frameStart, frameEnd; |
|
1095 nsDirection direction; |
|
1096 |
|
1097 nsPrevNextBidiLevels levels; |
|
1098 levels.SetData(nullptr, nullptr, 0, 0); |
|
1099 |
|
1100 currentFrame = GetFrameForNodeOffset(aNode, aContentOffset, |
|
1101 aHint, ¤tOffset); |
|
1102 if (!currentFrame) |
|
1103 return levels; |
|
1104 |
|
1105 currentFrame->GetOffsets(frameStart, frameEnd); |
|
1106 |
|
1107 if (0 == frameStart && 0 == frameEnd) |
|
1108 direction = eDirPrevious; |
|
1109 else if (frameStart == currentOffset) |
|
1110 direction = eDirPrevious; |
|
1111 else if (frameEnd == currentOffset) |
|
1112 direction = eDirNext; |
|
1113 else { |
|
1114 // we are neither at the beginning nor at the end of the frame, so we have no worries |
|
1115 levels.SetData(currentFrame, currentFrame, |
|
1116 NS_GET_EMBEDDING_LEVEL(currentFrame), |
|
1117 NS_GET_EMBEDDING_LEVEL(currentFrame)); |
|
1118 return levels; |
|
1119 } |
|
1120 |
|
1121 nsIFrame *newFrame; |
|
1122 int32_t offset; |
|
1123 bool jumpedLine; |
|
1124 nsresult rv = currentFrame->GetFrameFromDirection(direction, false, |
|
1125 aJumpLines, true, |
|
1126 &newFrame, &offset, &jumpedLine); |
|
1127 if (NS_FAILED(rv)) |
|
1128 newFrame = nullptr; |
|
1129 |
|
1130 uint8_t baseLevel = NS_GET_BASE_LEVEL(currentFrame); |
|
1131 uint8_t currentLevel = NS_GET_EMBEDDING_LEVEL(currentFrame); |
|
1132 uint8_t newLevel = newFrame ? NS_GET_EMBEDDING_LEVEL(newFrame) : baseLevel; |
|
1133 |
|
1134 // If not jumping lines, disregard br frames, since they might be positioned incorrectly. |
|
1135 // XXX This could be removed once bug 339786 is fixed. |
|
1136 if (!aJumpLines) { |
|
1137 if (currentFrame->GetType() == nsGkAtoms::brFrame) { |
|
1138 currentFrame = nullptr; |
|
1139 currentLevel = baseLevel; |
|
1140 } |
|
1141 if (newFrame && newFrame->GetType() == nsGkAtoms::brFrame) { |
|
1142 newFrame = nullptr; |
|
1143 newLevel = baseLevel; |
|
1144 } |
|
1145 } |
|
1146 |
|
1147 if (direction == eDirNext) |
|
1148 levels.SetData(currentFrame, newFrame, currentLevel, newLevel); |
|
1149 else |
|
1150 levels.SetData(newFrame, currentFrame, newLevel, currentLevel); |
|
1151 |
|
1152 return levels; |
|
1153 } |
|
1154 |
|
1155 nsresult |
|
1156 nsFrameSelection::GetFrameFromLevel(nsIFrame *aFrameIn, |
|
1157 nsDirection aDirection, |
|
1158 uint8_t aBidiLevel, |
|
1159 nsIFrame **aFrameOut) const |
|
1160 { |
|
1161 NS_ENSURE_STATE(mShell); |
|
1162 uint8_t foundLevel = 0; |
|
1163 nsIFrame *foundFrame = aFrameIn; |
|
1164 |
|
1165 nsCOMPtr<nsIFrameEnumerator> frameTraversal; |
|
1166 nsresult result; |
|
1167 nsCOMPtr<nsIFrameTraversal> trav(do_CreateInstance(kFrameTraversalCID,&result)); |
|
1168 if (NS_FAILED(result)) |
|
1169 return result; |
|
1170 |
|
1171 result = trav->NewFrameTraversal(getter_AddRefs(frameTraversal), |
|
1172 mShell->GetPresContext(), aFrameIn, |
|
1173 eLeaf, |
|
1174 false, // aVisual |
|
1175 false, // aLockInScrollView |
|
1176 false // aFollowOOFs |
|
1177 ); |
|
1178 if (NS_FAILED(result)) |
|
1179 return result; |
|
1180 |
|
1181 do { |
|
1182 *aFrameOut = foundFrame; |
|
1183 if (aDirection == eDirNext) |
|
1184 frameTraversal->Next(); |
|
1185 else |
|
1186 frameTraversal->Prev(); |
|
1187 |
|
1188 foundFrame = frameTraversal->CurrentItem(); |
|
1189 if (!foundFrame) |
|
1190 return NS_ERROR_FAILURE; |
|
1191 foundLevel = NS_GET_EMBEDDING_LEVEL(foundFrame); |
|
1192 |
|
1193 } while (foundLevel > aBidiLevel); |
|
1194 |
|
1195 return NS_OK; |
|
1196 } |
|
1197 |
|
1198 |
|
1199 nsresult |
|
1200 nsFrameSelection::MaintainSelection(nsSelectionAmount aAmount) |
|
1201 { |
|
1202 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
1203 if (!mDomSelections[index]) |
|
1204 return NS_ERROR_NULL_POINTER; |
|
1205 |
|
1206 mMaintainedAmount = aAmount; |
|
1207 |
|
1208 const nsRange* anchorFocusRange = |
|
1209 mDomSelections[index]->GetAnchorFocusRange(); |
|
1210 if (anchorFocusRange) { |
|
1211 mMaintainRange = anchorFocusRange->CloneRange(); |
|
1212 return NS_OK; |
|
1213 } |
|
1214 |
|
1215 mMaintainRange = nullptr; |
|
1216 return NS_OK; |
|
1217 } |
|
1218 |
|
1219 |
|
1220 /** After moving the caret, its Bidi level is set according to the following rules: |
|
1221 * |
|
1222 * After moving over a character with left/right arrow, set to the Bidi level of the last moved over character. |
|
1223 * After Home and End, set to the paragraph embedding level. |
|
1224 * After up/down arrow, PageUp/Down, set to the lower level of the 2 surrounding characters. |
|
1225 * After mouse click, set to the level of the current frame. |
|
1226 * |
|
1227 * The following two methods use GetPrevNextBidiLevels to determine the new Bidi level. |
|
1228 * BidiLevelFromMove is called when the caret is moved in response to a keyboard event |
|
1229 * |
|
1230 * @param aPresShell is the presentation shell |
|
1231 * @param aNode is the content node |
|
1232 * @param aContentOffset is the new caret position, as an offset into aNode |
|
1233 * @param aKeycode is the keyboard event that moved the caret to the new position |
|
1234 * @param aHint is the hint indicating in what logical direction the caret moved |
|
1235 */ |
|
1236 void nsFrameSelection::BidiLevelFromMove(nsIPresShell* aPresShell, |
|
1237 nsIContent *aNode, |
|
1238 uint32_t aContentOffset, |
|
1239 uint32_t aKeycode, |
|
1240 HINT aHint) |
|
1241 { |
|
1242 switch (aKeycode) { |
|
1243 |
|
1244 // Right and Left: the new cursor Bidi level is the level of the character moved over |
|
1245 case nsIDOMKeyEvent::DOM_VK_RIGHT: |
|
1246 case nsIDOMKeyEvent::DOM_VK_LEFT: |
|
1247 { |
|
1248 nsPrevNextBidiLevels levels = GetPrevNextBidiLevels(aNode, aContentOffset, |
|
1249 aHint, false); |
|
1250 |
|
1251 if (HINTLEFT == aHint) |
|
1252 SetCaretBidiLevel(levels.mLevelBefore); |
|
1253 else |
|
1254 SetCaretBidiLevel(levels.mLevelAfter); |
|
1255 break; |
|
1256 } |
|
1257 /* |
|
1258 // Up and Down: the new cursor Bidi level is the smaller of the two surrounding characters |
|
1259 case nsIDOMKeyEvent::DOM_VK_UP: |
|
1260 case nsIDOMKeyEvent::DOM_VK_DOWN: |
|
1261 GetPrevNextBidiLevels(aContext, aNode, aContentOffset, &firstFrame, &secondFrame, &firstLevel, &secondLevel); |
|
1262 aPresShell->SetCaretBidiLevel(std::min(firstLevel, secondLevel)); |
|
1263 break; |
|
1264 */ |
|
1265 |
|
1266 default: |
|
1267 UndefineCaretBidiLevel(); |
|
1268 } |
|
1269 } |
|
1270 |
|
1271 /** |
|
1272 * BidiLevelFromClick is called when the caret is repositioned by clicking the mouse |
|
1273 * |
|
1274 * @param aNode is the content node |
|
1275 * @param aContentOffset is the new caret position, as an offset into aNode |
|
1276 */ |
|
1277 void nsFrameSelection::BidiLevelFromClick(nsIContent *aNode, |
|
1278 uint32_t aContentOffset) |
|
1279 { |
|
1280 nsIFrame* clickInFrame=nullptr; |
|
1281 int32_t OffsetNotUsed; |
|
1282 |
|
1283 clickInFrame = GetFrameForNodeOffset(aNode, aContentOffset, mHint, &OffsetNotUsed); |
|
1284 if (!clickInFrame) |
|
1285 return; |
|
1286 |
|
1287 SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(clickInFrame)); |
|
1288 } |
|
1289 |
|
1290 |
|
1291 bool |
|
1292 nsFrameSelection::AdjustForMaintainedSelection(nsIContent *aContent, |
|
1293 int32_t aOffset) |
|
1294 { |
|
1295 if (!mMaintainRange) |
|
1296 return false; |
|
1297 |
|
1298 if (!aContent) { |
|
1299 return false; |
|
1300 } |
|
1301 |
|
1302 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
1303 if (!mDomSelections[index]) |
|
1304 return false; |
|
1305 |
|
1306 nsINode* rangeStartNode = mMaintainRange->GetStartParent(); |
|
1307 nsINode* rangeEndNode = mMaintainRange->GetEndParent(); |
|
1308 int32_t rangeStartOffset = mMaintainRange->StartOffset(); |
|
1309 int32_t rangeEndOffset = mMaintainRange->EndOffset(); |
|
1310 |
|
1311 int32_t relToStart = |
|
1312 nsContentUtils::ComparePoints(rangeStartNode, rangeStartOffset, |
|
1313 aContent, aOffset); |
|
1314 int32_t relToEnd = |
|
1315 nsContentUtils::ComparePoints(rangeEndNode, rangeEndOffset, |
|
1316 aContent, aOffset); |
|
1317 |
|
1318 // If aContent/aOffset is inside the maintained selection, or if it is on the |
|
1319 // "anchor" side of the maintained selection, we need to do something. |
|
1320 if ((relToStart < 0 && relToEnd > 0) || |
|
1321 (relToStart > 0 && |
|
1322 mDomSelections[index]->GetDirection() == eDirNext) || |
|
1323 (relToEnd < 0 && |
|
1324 mDomSelections[index]->GetDirection() == eDirPrevious)) { |
|
1325 // Set the current range to the maintained range. |
|
1326 mDomSelections[index]->ReplaceAnchorFocusRange(mMaintainRange); |
|
1327 if (relToStart < 0 && relToEnd > 0) { |
|
1328 // We're inside the maintained selection, just keep it selected. |
|
1329 return true; |
|
1330 } |
|
1331 // Reverse the direction of the selection so that the anchor will be on the |
|
1332 // far side of the maintained selection, relative to aContent/aOffset. |
|
1333 mDomSelections[index]->SetDirection(relToStart > 0 ? eDirPrevious : eDirNext); |
|
1334 } |
|
1335 return false; |
|
1336 } |
|
1337 |
|
1338 |
|
1339 nsresult |
|
1340 nsFrameSelection::HandleClick(nsIContent *aNewFocus, |
|
1341 uint32_t aContentOffset, |
|
1342 uint32_t aContentEndOffset, |
|
1343 bool aContinueSelection, |
|
1344 bool aMultipleSelection, |
|
1345 bool aHint) |
|
1346 { |
|
1347 if (!aNewFocus) |
|
1348 return NS_ERROR_INVALID_ARG; |
|
1349 |
|
1350 InvalidateDesiredX(); |
|
1351 |
|
1352 if (!aContinueSelection) { |
|
1353 mMaintainRange = nullptr; |
|
1354 if (!IsValidSelectionPoint(this, aNewFocus)) { |
|
1355 mAncestorLimiter = nullptr; |
|
1356 } |
|
1357 } |
|
1358 |
|
1359 // Don't take focus when dragging off of a table |
|
1360 if (!mDragSelectingCells) |
|
1361 { |
|
1362 BidiLevelFromClick(aNewFocus, aContentOffset); |
|
1363 PostReason(nsISelectionListener::MOUSEDOWN_REASON + nsISelectionListener::DRAG_REASON); |
|
1364 if (aContinueSelection && |
|
1365 AdjustForMaintainedSelection(aNewFocus, aContentOffset)) |
|
1366 return NS_OK; //shift clicked to maintained selection. rejected. |
|
1367 |
|
1368 return TakeFocus(aNewFocus, aContentOffset, aContentEndOffset, HINT(aHint), |
|
1369 aContinueSelection, aMultipleSelection); |
|
1370 } |
|
1371 |
|
1372 return NS_OK; |
|
1373 } |
|
1374 |
|
1375 void |
|
1376 nsFrameSelection::HandleDrag(nsIFrame *aFrame, nsPoint aPoint) |
|
1377 { |
|
1378 if (!aFrame || !mShell) |
|
1379 return; |
|
1380 |
|
1381 nsresult result; |
|
1382 nsIFrame *newFrame = 0; |
|
1383 nsPoint newPoint; |
|
1384 |
|
1385 result = ConstrainFrameAndPointToAnchorSubtree(aFrame, aPoint, &newFrame, newPoint); |
|
1386 if (NS_FAILED(result)) |
|
1387 return; |
|
1388 if (!newFrame) |
|
1389 return; |
|
1390 |
|
1391 nsIFrame::ContentOffsets offsets = |
|
1392 newFrame->GetContentOffsetsFromPoint(newPoint); |
|
1393 if (!offsets.content) |
|
1394 return; |
|
1395 |
|
1396 if (newFrame->IsSelected() && |
|
1397 AdjustForMaintainedSelection(offsets.content, offsets.offset)) |
|
1398 return; |
|
1399 |
|
1400 // Adjust offsets according to maintained amount |
|
1401 if (mMaintainRange && |
|
1402 mMaintainedAmount != eSelectNoAmount) { |
|
1403 |
|
1404 nsINode* rangenode = mMaintainRange->GetStartParent(); |
|
1405 int32_t rangeOffset = mMaintainRange->StartOffset(); |
|
1406 int32_t relativePosition = |
|
1407 nsContentUtils::ComparePoints(rangenode, rangeOffset, |
|
1408 offsets.content, offsets.offset); |
|
1409 |
|
1410 nsDirection direction = relativePosition > 0 ? eDirPrevious : eDirNext; |
|
1411 nsSelectionAmount amount = mMaintainedAmount; |
|
1412 if (amount == eSelectBeginLine && direction == eDirNext) |
|
1413 amount = eSelectEndLine; |
|
1414 |
|
1415 int32_t offset; |
|
1416 nsIFrame* frame = GetFrameForNodeOffset(offsets.content, offsets.offset, HINTRIGHT, &offset); |
|
1417 |
|
1418 if (frame && amount == eSelectWord && direction == eDirPrevious) { |
|
1419 // To avoid selecting the previous word when at start of word, |
|
1420 // first move one character forward. |
|
1421 nsPeekOffsetStruct charPos(eSelectCharacter, eDirNext, offset, 0, |
|
1422 false, mLimiter != nullptr, false, false); |
|
1423 if (NS_SUCCEEDED(frame->PeekOffset(&charPos))) { |
|
1424 frame = charPos.mResultFrame; |
|
1425 offset = charPos.mContentOffset; |
|
1426 } |
|
1427 } |
|
1428 |
|
1429 nsPeekOffsetStruct pos(amount, direction, offset, 0, |
|
1430 false, mLimiter != nullptr, false, false); |
|
1431 |
|
1432 if (frame && NS_SUCCEEDED(frame->PeekOffset(&pos)) && pos.mResultContent) { |
|
1433 offsets.content = pos.mResultContent; |
|
1434 offsets.offset = pos.mContentOffset; |
|
1435 } |
|
1436 } |
|
1437 |
|
1438 HandleClick(offsets.content, offsets.offset, offsets.offset, |
|
1439 true, false, offsets.associateWithNext); |
|
1440 } |
|
1441 |
|
1442 nsresult |
|
1443 nsFrameSelection::StartAutoScrollTimer(nsIFrame *aFrame, |
|
1444 nsPoint aPoint, |
|
1445 uint32_t aDelay) |
|
1446 { |
|
1447 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
1448 if (!mDomSelections[index]) |
|
1449 return NS_ERROR_NULL_POINTER; |
|
1450 |
|
1451 return mDomSelections[index]->StartAutoScrollTimer(aFrame, aPoint, aDelay); |
|
1452 } |
|
1453 |
|
1454 void |
|
1455 nsFrameSelection::StopAutoScrollTimer() |
|
1456 { |
|
1457 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
1458 if (!mDomSelections[index]) |
|
1459 return; |
|
1460 |
|
1461 mDomSelections[index]->StopAutoScrollTimer(); |
|
1462 } |
|
1463 |
|
1464 /** |
|
1465 hard to go from nodes to frames, easy the other way! |
|
1466 */ |
|
1467 nsresult |
|
1468 nsFrameSelection::TakeFocus(nsIContent *aNewFocus, |
|
1469 uint32_t aContentOffset, |
|
1470 uint32_t aContentEndOffset, |
|
1471 HINT aHint, |
|
1472 bool aContinueSelection, |
|
1473 bool aMultipleSelection) |
|
1474 { |
|
1475 if (!aNewFocus) |
|
1476 return NS_ERROR_NULL_POINTER; |
|
1477 |
|
1478 NS_ENSURE_STATE(mShell); |
|
1479 |
|
1480 if (!IsValidSelectionPoint(this,aNewFocus)) |
|
1481 return NS_ERROR_FAILURE; |
|
1482 |
|
1483 // Clear all table selection data |
|
1484 mSelectingTableCellMode = 0; |
|
1485 mDragSelectingCells = false; |
|
1486 mStartSelectedCell = nullptr; |
|
1487 mEndSelectedCell = nullptr; |
|
1488 mAppendStartSelectedCell = nullptr; |
|
1489 mHint = aHint; |
|
1490 |
|
1491 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
1492 if (!mDomSelections[index]) |
|
1493 return NS_ERROR_NULL_POINTER; |
|
1494 |
|
1495 //traverse through document and unselect crap here |
|
1496 if (!aContinueSelection) {//single click? setting cursor down |
|
1497 uint32_t batching = mBatching;//hack to use the collapse code. |
|
1498 bool changes = mChangesDuringBatching; |
|
1499 mBatching = 1; |
|
1500 |
|
1501 if (aMultipleSelection) { |
|
1502 // Remove existing collapsed ranges as there's no point in having |
|
1503 // non-anchor/focus collapsed ranges. |
|
1504 mDomSelections[index]->RemoveCollapsedRanges(); |
|
1505 |
|
1506 nsRefPtr<nsRange> newRange = new nsRange(aNewFocus); |
|
1507 |
|
1508 newRange->SetStart(aNewFocus, aContentOffset); |
|
1509 newRange->SetEnd(aNewFocus, aContentOffset); |
|
1510 mDomSelections[index]->AddRange(newRange); |
|
1511 mBatching = batching; |
|
1512 mChangesDuringBatching = changes; |
|
1513 } |
|
1514 else |
|
1515 { |
|
1516 bool oldDesiredXSet = mDesiredXSet; //need to keep old desired X if it was set. |
|
1517 mDomSelections[index]->Collapse(aNewFocus, aContentOffset); |
|
1518 mDesiredXSet = oldDesiredXSet; //now reset desired X back. |
|
1519 mBatching = batching; |
|
1520 mChangesDuringBatching = changes; |
|
1521 } |
|
1522 if (aContentEndOffset != aContentOffset) |
|
1523 mDomSelections[index]->Extend(aNewFocus, aContentEndOffset); |
|
1524 |
|
1525 //find out if we are inside a table. if so, find out which one and which cell |
|
1526 //once we do that, the next time we get a takefocus, check the parent tree. |
|
1527 //if we are no longer inside same table ,cell then switch to table selection mode. |
|
1528 // BUT only do this in an editor |
|
1529 |
|
1530 NS_ENSURE_STATE(mShell); |
|
1531 int16_t displaySelection = mShell->GetSelectionFlags(); |
|
1532 |
|
1533 // Editor has DISPLAY_ALL selection type |
|
1534 if (displaySelection == nsISelectionDisplay::DISPLAY_ALL) |
|
1535 { |
|
1536 mCellParent = GetCellParent(aNewFocus); |
|
1537 #ifdef DEBUG_TABLE_SELECTION |
|
1538 if (mCellParent) |
|
1539 printf(" * TakeFocus - Collapsing into new cell\n"); |
|
1540 #endif |
|
1541 } |
|
1542 } |
|
1543 else { |
|
1544 // Now update the range list: |
|
1545 if (aContinueSelection && aNewFocus) |
|
1546 { |
|
1547 int32_t offset; |
|
1548 nsINode *cellparent = GetCellParent(aNewFocus); |
|
1549 if (mCellParent && cellparent && cellparent != mCellParent) //switch to cell selection mode |
|
1550 { |
|
1551 #ifdef DEBUG_TABLE_SELECTION |
|
1552 printf(" * TakeFocus - moving into new cell\n"); |
|
1553 #endif |
|
1554 WidgetMouseEvent event(false, 0, nullptr, WidgetMouseEvent::eReal); |
|
1555 |
|
1556 // Start selecting in the cell we were in before |
|
1557 nsINode* parent = ParentOffset(mCellParent, &offset); |
|
1558 if (parent) |
|
1559 HandleTableSelection(parent, offset, |
|
1560 nsISelectionPrivate::TABLESELECTION_CELL, &event); |
|
1561 |
|
1562 // Find the parent of this new cell and extend selection to it |
|
1563 parent = ParentOffset(cellparent, &offset); |
|
1564 |
|
1565 // XXXX We need to REALLY get the current key shift state |
|
1566 // (we'd need to add event listener -- let's not bother for now) |
|
1567 event.modifiers &= ~MODIFIER_SHIFT; //aContinueSelection; |
|
1568 if (parent) |
|
1569 { |
|
1570 mCellParent = cellparent; |
|
1571 // Continue selection into next cell |
|
1572 HandleTableSelection(parent, offset, |
|
1573 nsISelectionPrivate::TABLESELECTION_CELL, &event); |
|
1574 } |
|
1575 } |
|
1576 else |
|
1577 { |
|
1578 // XXXX Problem: Shift+click in browser is appending text selection to selected table!!! |
|
1579 // is this the place to erase seleced cells ????? |
|
1580 if (mDomSelections[index]->GetDirection() == eDirNext && aContentEndOffset > aContentOffset) //didn't go far enough |
|
1581 { |
|
1582 mDomSelections[index]->Extend(aNewFocus, aContentEndOffset);//this will only redraw the diff |
|
1583 } |
|
1584 else |
|
1585 mDomSelections[index]->Extend(aNewFocus, aContentOffset); |
|
1586 } |
|
1587 } |
|
1588 } |
|
1589 |
|
1590 // Don't notify selection listeners if batching is on: |
|
1591 if (GetBatching()) |
|
1592 return NS_OK; |
|
1593 return NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL); |
|
1594 } |
|
1595 |
|
1596 |
|
1597 SelectionDetails* |
|
1598 nsFrameSelection::LookUpSelection(nsIContent *aContent, |
|
1599 int32_t aContentOffset, |
|
1600 int32_t aContentLength, |
|
1601 bool aSlowCheck) const |
|
1602 { |
|
1603 if (!aContent || !mShell) |
|
1604 return nullptr; |
|
1605 |
|
1606 SelectionDetails* details = nullptr; |
|
1607 |
|
1608 for (int32_t j = 0; j < nsISelectionController::NUM_SELECTIONTYPES; j++) { |
|
1609 if (mDomSelections[j]) { |
|
1610 mDomSelections[j]->LookUpSelection(aContent, aContentOffset, |
|
1611 aContentLength, &details, (SelectionType)(1<<j), aSlowCheck); |
|
1612 } |
|
1613 } |
|
1614 |
|
1615 return details; |
|
1616 } |
|
1617 |
|
1618 void |
|
1619 nsFrameSelection::SetMouseDownState(bool aState) |
|
1620 { |
|
1621 if (mMouseDownState == aState) |
|
1622 return; |
|
1623 |
|
1624 mMouseDownState = aState; |
|
1625 |
|
1626 if (!mMouseDownState) |
|
1627 { |
|
1628 mDragSelectingCells = false; |
|
1629 PostReason(nsISelectionListener::MOUSEUP_REASON); |
|
1630 NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL); //notify that reason is mouse up please. |
|
1631 } |
|
1632 } |
|
1633 |
|
1634 Selection* |
|
1635 nsFrameSelection::GetSelection(SelectionType aType) const |
|
1636 { |
|
1637 int8_t index = GetIndexFromSelectionType(aType); |
|
1638 if (index < 0) |
|
1639 return nullptr; |
|
1640 |
|
1641 return mDomSelections[index]; |
|
1642 } |
|
1643 |
|
1644 nsresult |
|
1645 nsFrameSelection::ScrollSelectionIntoView(SelectionType aType, |
|
1646 SelectionRegion aRegion, |
|
1647 int16_t aFlags) const |
|
1648 { |
|
1649 int8_t index = GetIndexFromSelectionType(aType); |
|
1650 if (index < 0) |
|
1651 return NS_ERROR_INVALID_ARG; |
|
1652 |
|
1653 if (!mDomSelections[index]) |
|
1654 return NS_ERROR_NULL_POINTER; |
|
1655 |
|
1656 nsIPresShell::ScrollAxis verticalScroll = nsIPresShell::ScrollAxis(); |
|
1657 int32_t flags = Selection::SCROLL_DO_FLUSH; |
|
1658 if (aFlags & nsISelectionController::SCROLL_SYNCHRONOUS) { |
|
1659 flags |= Selection::SCROLL_SYNCHRONOUS; |
|
1660 } else if (aFlags & nsISelectionController::SCROLL_FIRST_ANCESTOR_ONLY) { |
|
1661 flags |= Selection::SCROLL_FIRST_ANCESTOR_ONLY; |
|
1662 } |
|
1663 if (aFlags & nsISelectionController::SCROLL_OVERFLOW_HIDDEN) { |
|
1664 flags |= Selection::SCROLL_OVERFLOW_HIDDEN; |
|
1665 } |
|
1666 if (aFlags & nsISelectionController::SCROLL_CENTER_VERTICALLY) { |
|
1667 verticalScroll = nsIPresShell::ScrollAxis( |
|
1668 nsIPresShell::SCROLL_CENTER, nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE); |
|
1669 } |
|
1670 |
|
1671 // After ScrollSelectionIntoView(), the pending notifications might be |
|
1672 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. |
|
1673 return mDomSelections[index]->ScrollIntoView(aRegion, |
|
1674 verticalScroll, |
|
1675 nsIPresShell::ScrollAxis(), |
|
1676 flags); |
|
1677 } |
|
1678 |
|
1679 nsresult |
|
1680 nsFrameSelection::RepaintSelection(SelectionType aType) const |
|
1681 { |
|
1682 int8_t index = GetIndexFromSelectionType(aType); |
|
1683 if (index < 0) |
|
1684 return NS_ERROR_INVALID_ARG; |
|
1685 if (!mDomSelections[index]) |
|
1686 return NS_ERROR_NULL_POINTER; |
|
1687 NS_ENSURE_STATE(mShell); |
|
1688 return mDomSelections[index]->Repaint(mShell->GetPresContext()); |
|
1689 } |
|
1690 |
|
1691 nsIFrame* |
|
1692 nsFrameSelection::GetFrameForNodeOffset(nsIContent *aNode, |
|
1693 int32_t aOffset, |
|
1694 HINT aHint, |
|
1695 int32_t *aReturnOffset) const |
|
1696 { |
|
1697 if (!aNode || !aReturnOffset || !mShell) |
|
1698 return nullptr; |
|
1699 |
|
1700 if (aOffset < 0) |
|
1701 return nullptr; |
|
1702 |
|
1703 *aReturnOffset = aOffset; |
|
1704 |
|
1705 nsCOMPtr<nsIContent> theNode = aNode; |
|
1706 |
|
1707 if (aNode->IsElement()) |
|
1708 { |
|
1709 int32_t childIndex = 0; |
|
1710 int32_t numChildren = theNode->GetChildCount(); |
|
1711 |
|
1712 if (aHint == HINTLEFT) |
|
1713 { |
|
1714 if (aOffset > 0) |
|
1715 childIndex = aOffset - 1; |
|
1716 else |
|
1717 childIndex = aOffset; |
|
1718 } |
|
1719 else // HINTRIGHT |
|
1720 { |
|
1721 if (aOffset >= numChildren) |
|
1722 { |
|
1723 if (numChildren > 0) |
|
1724 childIndex = numChildren - 1; |
|
1725 else |
|
1726 childIndex = 0; |
|
1727 } |
|
1728 else |
|
1729 childIndex = aOffset; |
|
1730 } |
|
1731 |
|
1732 if (childIndex > 0 || numChildren > 0) { |
|
1733 nsCOMPtr<nsIContent> childNode = theNode->GetChildAt(childIndex); |
|
1734 |
|
1735 if (!childNode) |
|
1736 return nullptr; |
|
1737 |
|
1738 theNode = childNode; |
|
1739 } |
|
1740 |
|
1741 #ifdef DONT_DO_THIS_YET |
|
1742 // XXX: We can't use this code yet because the hinting |
|
1743 // can cause us to attach to the wrong line frame. |
|
1744 |
|
1745 // Now that we have the child node, check if it too |
|
1746 // can contain children. If so, call this method again! |
|
1747 |
|
1748 if (theNode->IsElement()) |
|
1749 { |
|
1750 int32_t newOffset = 0; |
|
1751 |
|
1752 if (aOffset > childIndex) |
|
1753 { |
|
1754 numChildren = theNode->GetChildCount(); |
|
1755 |
|
1756 newOffset = numChildren; |
|
1757 } |
|
1758 |
|
1759 return GetFrameForNodeOffset(theNode, newOffset, aHint, aReturnOffset); |
|
1760 } |
|
1761 else |
|
1762 #endif // DONT_DO_THIS_YET |
|
1763 { |
|
1764 // Check to see if theNode is a text node. If it is, translate |
|
1765 // aOffset into an offset into the text node. |
|
1766 |
|
1767 nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(theNode); |
|
1768 |
|
1769 if (textNode) |
|
1770 { |
|
1771 if (theNode->GetPrimaryFrame()) |
|
1772 { |
|
1773 if (aOffset > childIndex) |
|
1774 { |
|
1775 uint32_t textLength = 0; |
|
1776 |
|
1777 nsresult rv = textNode->GetLength(&textLength); |
|
1778 if (NS_FAILED(rv)) |
|
1779 return nullptr; |
|
1780 |
|
1781 *aReturnOffset = (int32_t)textLength; |
|
1782 } |
|
1783 else |
|
1784 *aReturnOffset = 0; |
|
1785 } |
|
1786 else |
|
1787 { |
|
1788 // If we're at a collapsed whitespace content node (which |
|
1789 // does not have a primary frame), just use the original node |
|
1790 // to get the frame on which we should put the caret. |
|
1791 theNode = aNode; |
|
1792 } |
|
1793 } |
|
1794 } |
|
1795 } |
|
1796 |
|
1797 // If the node is a ShadowRoot, the frame needs to be adjusted, |
|
1798 // because a ShadowRoot does not get a frame. Its children are rendered |
|
1799 // as children of the host. |
|
1800 mozilla::dom::ShadowRoot* shadowRoot = |
|
1801 mozilla::dom::ShadowRoot::FromNode(theNode); |
|
1802 if (shadowRoot) { |
|
1803 theNode = shadowRoot->GetHost(); |
|
1804 } |
|
1805 |
|
1806 nsIFrame* returnFrame = theNode->GetPrimaryFrame(); |
|
1807 if (!returnFrame) |
|
1808 return nullptr; |
|
1809 |
|
1810 // find the child frame containing the offset we want |
|
1811 returnFrame->GetChildFrameContainingOffset(*aReturnOffset, aHint == HINTRIGHT, |
|
1812 &aOffset, &returnFrame); |
|
1813 return returnFrame; |
|
1814 } |
|
1815 |
|
1816 void |
|
1817 nsFrameSelection::CommonPageMove(bool aForward, |
|
1818 bool aExtend, |
|
1819 nsIScrollableFrame* aScrollableFrame) |
|
1820 { |
|
1821 // expected behavior for PageMove is to scroll AND move the caret |
|
1822 // and remain relative position of the caret in view. see Bug 4302. |
|
1823 |
|
1824 //get the frame from the scrollable view |
|
1825 |
|
1826 nsIFrame* scrolledFrame = aScrollableFrame->GetScrolledFrame(); |
|
1827 if (!scrolledFrame) |
|
1828 return; |
|
1829 |
|
1830 // find out where the caret is. |
|
1831 // we should know mDesiredX value of nsFrameSelection, but I havent seen that behavior in other windows applications yet. |
|
1832 nsISelection* domSel = GetSelection(nsISelectionController::SELECTION_NORMAL); |
|
1833 if (!domSel) |
|
1834 return; |
|
1835 |
|
1836 nsRefPtr<nsCaret> caret = mShell->GetCaret(); |
|
1837 |
|
1838 nsRect caretPos; |
|
1839 nsIFrame* caretFrame = caret->GetGeometry(domSel, &caretPos); |
|
1840 if (!caretFrame) |
|
1841 return; |
|
1842 |
|
1843 //need to adjust caret jump by percentage scroll |
|
1844 nsSize scrollDelta = aScrollableFrame->GetPageScrollAmount(); |
|
1845 |
|
1846 if (aForward) |
|
1847 caretPos.y += scrollDelta.height; |
|
1848 else |
|
1849 caretPos.y -= scrollDelta.height; |
|
1850 |
|
1851 caretPos += caretFrame->GetOffsetTo(scrolledFrame); |
|
1852 |
|
1853 // get a content at desired location |
|
1854 nsPoint desiredPoint; |
|
1855 desiredPoint.x = caretPos.x; |
|
1856 desiredPoint.y = caretPos.y + caretPos.height/2; |
|
1857 nsIFrame::ContentOffsets offsets = |
|
1858 scrolledFrame->GetContentOffsetsFromPoint(desiredPoint); |
|
1859 |
|
1860 if (!offsets.content) |
|
1861 return; |
|
1862 |
|
1863 // scroll one page |
|
1864 aScrollableFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), |
|
1865 nsIScrollableFrame::PAGES, |
|
1866 nsIScrollableFrame::SMOOTH); |
|
1867 |
|
1868 // place the caret |
|
1869 HandleClick(offsets.content, offsets.offset, |
|
1870 offsets.offset, aExtend, false, true); |
|
1871 } |
|
1872 |
|
1873 nsresult |
|
1874 nsFrameSelection::CharacterMove(bool aForward, bool aExtend) |
|
1875 { |
|
1876 if (aForward) |
|
1877 return MoveCaret(nsIDOMKeyEvent::DOM_VK_RIGHT, aExtend, eSelectCluster); |
|
1878 else |
|
1879 return MoveCaret(nsIDOMKeyEvent::DOM_VK_LEFT, aExtend, eSelectCluster); |
|
1880 } |
|
1881 |
|
1882 nsresult |
|
1883 nsFrameSelection::CharacterExtendForDelete() |
|
1884 { |
|
1885 return MoveCaret(nsIDOMKeyEvent::DOM_VK_DELETE, true, eSelectCluster); |
|
1886 } |
|
1887 |
|
1888 nsresult |
|
1889 nsFrameSelection::CharacterExtendForBackspace() |
|
1890 { |
|
1891 return MoveCaret(nsIDOMKeyEvent::DOM_VK_BACK_SPACE, true, eSelectCharacter); |
|
1892 } |
|
1893 |
|
1894 nsresult |
|
1895 nsFrameSelection::WordMove(bool aForward, bool aExtend) |
|
1896 { |
|
1897 if (aForward) |
|
1898 return MoveCaret(nsIDOMKeyEvent::DOM_VK_RIGHT,aExtend,eSelectWord); |
|
1899 else |
|
1900 return MoveCaret(nsIDOMKeyEvent::DOM_VK_LEFT,aExtend,eSelectWord); |
|
1901 } |
|
1902 |
|
1903 nsresult |
|
1904 nsFrameSelection::WordExtendForDelete(bool aForward) |
|
1905 { |
|
1906 if (aForward) |
|
1907 return MoveCaret(nsIDOMKeyEvent::DOM_VK_DELETE, true, eSelectWord); |
|
1908 else |
|
1909 return MoveCaret(nsIDOMKeyEvent::DOM_VK_BACK_SPACE, true, eSelectWord); |
|
1910 } |
|
1911 |
|
1912 nsresult |
|
1913 nsFrameSelection::LineMove(bool aForward, bool aExtend) |
|
1914 { |
|
1915 if (aForward) |
|
1916 return MoveCaret(nsIDOMKeyEvent::DOM_VK_DOWN,aExtend,eSelectLine); |
|
1917 else |
|
1918 return MoveCaret(nsIDOMKeyEvent::DOM_VK_UP,aExtend,eSelectLine); |
|
1919 } |
|
1920 |
|
1921 nsresult |
|
1922 nsFrameSelection::IntraLineMove(bool aForward, bool aExtend) |
|
1923 { |
|
1924 if (aForward) |
|
1925 return MoveCaret(nsIDOMKeyEvent::DOM_VK_END,aExtend,eSelectLine); |
|
1926 else |
|
1927 return MoveCaret(nsIDOMKeyEvent::DOM_VK_HOME,aExtend,eSelectLine); |
|
1928 } |
|
1929 |
|
1930 nsresult |
|
1931 nsFrameSelection::SelectAll() |
|
1932 { |
|
1933 nsCOMPtr<nsIContent> rootContent; |
|
1934 if (mLimiter) |
|
1935 { |
|
1936 rootContent = mLimiter;//addrefit |
|
1937 } |
|
1938 else if (mAncestorLimiter) { |
|
1939 rootContent = mAncestorLimiter; |
|
1940 } |
|
1941 else |
|
1942 { |
|
1943 NS_ENSURE_STATE(mShell); |
|
1944 nsIDocument *doc = mShell->GetDocument(); |
|
1945 if (!doc) |
|
1946 return NS_ERROR_FAILURE; |
|
1947 rootContent = doc->GetRootElement(); |
|
1948 if (!rootContent) |
|
1949 return NS_ERROR_FAILURE; |
|
1950 } |
|
1951 int32_t numChildren = rootContent->GetChildCount(); |
|
1952 PostReason(nsISelectionListener::NO_REASON); |
|
1953 return TakeFocus(rootContent, 0, numChildren, HINTLEFT, false, false); |
|
1954 } |
|
1955 |
|
1956 //////////END FRAMESELECTION |
|
1957 |
|
1958 void |
|
1959 nsFrameSelection::StartBatchChanges() |
|
1960 { |
|
1961 mBatching++; |
|
1962 } |
|
1963 |
|
1964 void |
|
1965 nsFrameSelection::EndBatchChanges() |
|
1966 { |
|
1967 mBatching--; |
|
1968 NS_ASSERTION(mBatching >=0,"Bad mBatching"); |
|
1969 if (mBatching == 0 && mChangesDuringBatching){ |
|
1970 mChangesDuringBatching = false; |
|
1971 NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL); |
|
1972 } |
|
1973 } |
|
1974 |
|
1975 |
|
1976 nsresult |
|
1977 nsFrameSelection::NotifySelectionListeners(SelectionType aType) |
|
1978 { |
|
1979 int8_t index = GetIndexFromSelectionType(aType); |
|
1980 if (index >=0 && mDomSelections[index]) |
|
1981 { |
|
1982 return mDomSelections[index]->NotifySelectionListeners(); |
|
1983 } |
|
1984 return NS_ERROR_FAILURE; |
|
1985 } |
|
1986 |
|
1987 // Start of Table Selection methods |
|
1988 |
|
1989 static bool IsCell(nsIContent *aContent) |
|
1990 { |
|
1991 return ((aContent->Tag() == nsGkAtoms::td || |
|
1992 aContent->Tag() == nsGkAtoms::th) && |
|
1993 aContent->IsHTML()); |
|
1994 } |
|
1995 |
|
1996 nsITableCellLayout* |
|
1997 nsFrameSelection::GetCellLayout(nsIContent *aCellContent) const |
|
1998 { |
|
1999 NS_ENSURE_TRUE(mShell, nullptr); |
|
2000 nsITableCellLayout *cellLayoutObject = |
|
2001 do_QueryFrame(aCellContent->GetPrimaryFrame()); |
|
2002 return cellLayoutObject; |
|
2003 } |
|
2004 |
|
2005 nsresult |
|
2006 nsFrameSelection::ClearNormalSelection() |
|
2007 { |
|
2008 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
2009 if (!mDomSelections[index]) |
|
2010 return NS_ERROR_NULL_POINTER; |
|
2011 |
|
2012 return mDomSelections[index]->RemoveAllRanges(); |
|
2013 } |
|
2014 |
|
2015 static nsIContent* |
|
2016 GetFirstSelectedContent(nsRange* aRange) |
|
2017 { |
|
2018 if (!aRange) { |
|
2019 return nullptr; |
|
2020 } |
|
2021 |
|
2022 NS_PRECONDITION(aRange->GetStartParent(), "Must have start parent!"); |
|
2023 NS_PRECONDITION(aRange->GetStartParent()->IsElement(), |
|
2024 "Unexpected parent"); |
|
2025 |
|
2026 return aRange->GetStartParent()->GetChildAt(aRange->StartOffset()); |
|
2027 } |
|
2028 |
|
2029 // Table selection support. |
|
2030 // TODO: Separate table methods into a separate nsITableSelection interface |
|
2031 nsresult |
|
2032 nsFrameSelection::HandleTableSelection(nsINode* aParentContent, |
|
2033 int32_t aContentOffset, |
|
2034 int32_t aTarget, |
|
2035 WidgetMouseEvent* aMouseEvent) |
|
2036 { |
|
2037 NS_ENSURE_TRUE(aParentContent, NS_ERROR_NULL_POINTER); |
|
2038 NS_ENSURE_TRUE(aMouseEvent, NS_ERROR_NULL_POINTER); |
|
2039 |
|
2040 if (mMouseDownState && mDragSelectingCells && (aTarget & nsISelectionPrivate::TABLESELECTION_TABLE)) |
|
2041 { |
|
2042 // We were selecting cells and user drags mouse in table border or inbetween cells, |
|
2043 // just do nothing |
|
2044 return NS_OK; |
|
2045 } |
|
2046 |
|
2047 nsresult result = NS_OK; |
|
2048 |
|
2049 nsIContent *childContent = aParentContent->GetChildAt(aContentOffset); |
|
2050 |
|
2051 // When doing table selection, always set the direction to next so |
|
2052 // we can be sure that anchorNode's offset always points to the |
|
2053 // selected cell |
|
2054 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
2055 if (!mDomSelections[index]) |
|
2056 return NS_ERROR_NULL_POINTER; |
|
2057 |
|
2058 mDomSelections[index]->SetDirection(eDirNext); |
|
2059 |
|
2060 // Stack-class to wrap all table selection changes in |
|
2061 // BeginBatchChanges() / EndBatchChanges() |
|
2062 nsSelectionBatcher selectionBatcher(mDomSelections[index]); |
|
2063 |
|
2064 int32_t startRowIndex, startColIndex, curRowIndex, curColIndex; |
|
2065 if (mMouseDownState && mDragSelectingCells) |
|
2066 { |
|
2067 // We are drag-selecting |
|
2068 if (aTarget != nsISelectionPrivate::TABLESELECTION_TABLE) |
|
2069 { |
|
2070 // If dragging in the same cell as last event, do nothing |
|
2071 if (mEndSelectedCell == childContent) |
|
2072 return NS_OK; |
|
2073 |
|
2074 #ifdef DEBUG_TABLE_SELECTION |
|
2075 printf(" mStartSelectedCell = %x, mEndSelectedCell = %x, childContent = %x \n", mStartSelectedCell, mEndSelectedCell, childContent); |
|
2076 #endif |
|
2077 // aTarget can be any "cell mode", |
|
2078 // so we can easily drag-select rows and columns |
|
2079 // Once we are in row or column mode, |
|
2080 // we can drift into any cell to stay in that mode |
|
2081 // even if aTarget = TABLESELECTION_CELL |
|
2082 |
|
2083 if (mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_ROW || |
|
2084 mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_COLUMN) |
|
2085 { |
|
2086 if (mEndSelectedCell) |
|
2087 { |
|
2088 // Also check if cell is in same row/col |
|
2089 result = GetCellIndexes(mEndSelectedCell, startRowIndex, startColIndex); |
|
2090 if (NS_FAILED(result)) return result; |
|
2091 result = GetCellIndexes(childContent, curRowIndex, curColIndex); |
|
2092 if (NS_FAILED(result)) return result; |
|
2093 |
|
2094 #ifdef DEBUG_TABLE_SELECTION |
|
2095 printf(" curRowIndex = %d, startRowIndex = %d, curColIndex = %d, startColIndex = %d\n", curRowIndex, startRowIndex, curColIndex, startColIndex); |
|
2096 #endif |
|
2097 if ((mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_ROW && startRowIndex == curRowIndex) || |
|
2098 (mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_COLUMN && startColIndex == curColIndex)) |
|
2099 return NS_OK; |
|
2100 } |
|
2101 #ifdef DEBUG_TABLE_SELECTION |
|
2102 printf(" Dragged into a new column or row\n"); |
|
2103 #endif |
|
2104 // Continue dragging row or column selection |
|
2105 return SelectRowOrColumn(childContent, mSelectingTableCellMode); |
|
2106 } |
|
2107 else if (mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_CELL) |
|
2108 { |
|
2109 #ifdef DEBUG_TABLE_SELECTION |
|
2110 printf("HandleTableSelection: Dragged into a new cell\n"); |
|
2111 #endif |
|
2112 // Trick for quick selection of rows and columns |
|
2113 // Hold down shift, then start selecting in one direction |
|
2114 // If next cell dragged into is in same row, select entire row, |
|
2115 // if next cell is in same column, select entire column |
|
2116 if (mStartSelectedCell && aMouseEvent->IsShift()) |
|
2117 { |
|
2118 result = GetCellIndexes(mStartSelectedCell, startRowIndex, startColIndex); |
|
2119 if (NS_FAILED(result)) return result; |
|
2120 result = GetCellIndexes(childContent, curRowIndex, curColIndex); |
|
2121 if (NS_FAILED(result)) return result; |
|
2122 |
|
2123 if (startRowIndex == curRowIndex || |
|
2124 startColIndex == curColIndex) |
|
2125 { |
|
2126 // Force new selection block |
|
2127 mStartSelectedCell = nullptr; |
|
2128 mDomSelections[index]->RemoveAllRanges(); |
|
2129 |
|
2130 if (startRowIndex == curRowIndex) |
|
2131 mSelectingTableCellMode = nsISelectionPrivate::TABLESELECTION_ROW; |
|
2132 else |
|
2133 mSelectingTableCellMode = nsISelectionPrivate::TABLESELECTION_COLUMN; |
|
2134 |
|
2135 return SelectRowOrColumn(childContent, mSelectingTableCellMode); |
|
2136 } |
|
2137 } |
|
2138 |
|
2139 // Reselect block of cells to new end location |
|
2140 return SelectBlockOfCells(mStartSelectedCell, childContent); |
|
2141 } |
|
2142 } |
|
2143 // Do nothing if dragging in table, but outside a cell |
|
2144 return NS_OK; |
|
2145 } |
|
2146 else |
|
2147 { |
|
2148 // Not dragging -- mouse event is down or up |
|
2149 if (mMouseDownState) |
|
2150 { |
|
2151 #ifdef DEBUG_TABLE_SELECTION |
|
2152 printf("HandleTableSelection: Mouse down event\n"); |
|
2153 #endif |
|
2154 // Clear cell we stored in mouse-down |
|
2155 mUnselectCellOnMouseUp = nullptr; |
|
2156 |
|
2157 if (aTarget == nsISelectionPrivate::TABLESELECTION_CELL) |
|
2158 { |
|
2159 bool isSelected = false; |
|
2160 |
|
2161 // Check if we have other selected cells |
|
2162 nsIContent* previousCellNode = |
|
2163 GetFirstSelectedContent(GetFirstCellRange()); |
|
2164 if (previousCellNode) |
|
2165 { |
|
2166 // We have at least 1 other selected cell |
|
2167 |
|
2168 // Check if new cell is already selected |
|
2169 nsIFrame *cellFrame = childContent->GetPrimaryFrame(); |
|
2170 if (!cellFrame) return NS_ERROR_NULL_POINTER; |
|
2171 isSelected = cellFrame->IsSelected(); |
|
2172 } |
|
2173 else |
|
2174 { |
|
2175 // No cells selected -- remove non-cell selection |
|
2176 mDomSelections[index]->RemoveAllRanges(); |
|
2177 } |
|
2178 mDragSelectingCells = true; // Signal to start drag-cell-selection |
|
2179 mSelectingTableCellMode = aTarget; |
|
2180 // Set start for new drag-selection block (not appended) |
|
2181 mStartSelectedCell = childContent; |
|
2182 // The initial block end is same as the start |
|
2183 mEndSelectedCell = childContent; |
|
2184 |
|
2185 if (isSelected) |
|
2186 { |
|
2187 // Remember this cell to (possibly) unselect it on mouseup |
|
2188 mUnselectCellOnMouseUp = childContent; |
|
2189 #ifdef DEBUG_TABLE_SELECTION |
|
2190 printf("HandleTableSelection: Saving mUnselectCellOnMouseUp\n"); |
|
2191 #endif |
|
2192 } |
|
2193 else |
|
2194 { |
|
2195 // Select an unselected cell |
|
2196 // but first remove existing selection if not in same table |
|
2197 if (previousCellNode && |
|
2198 !IsInSameTable(previousCellNode, childContent)) |
|
2199 { |
|
2200 mDomSelections[index]->RemoveAllRanges(); |
|
2201 // Reset selection mode that is cleared in RemoveAllRanges |
|
2202 mSelectingTableCellMode = aTarget; |
|
2203 } |
|
2204 |
|
2205 return SelectCellElement(childContent); |
|
2206 } |
|
2207 |
|
2208 return NS_OK; |
|
2209 } |
|
2210 else if (aTarget == nsISelectionPrivate::TABLESELECTION_TABLE) |
|
2211 { |
|
2212 //TODO: We currently select entire table when clicked between cells, |
|
2213 // should we restrict to only around border? |
|
2214 // *** How do we get location data for cell and click? |
|
2215 mDragSelectingCells = false; |
|
2216 mStartSelectedCell = nullptr; |
|
2217 mEndSelectedCell = nullptr; |
|
2218 |
|
2219 // Remove existing selection and select the table |
|
2220 mDomSelections[index]->RemoveAllRanges(); |
|
2221 return CreateAndAddRange(aParentContent, aContentOffset); |
|
2222 } |
|
2223 else if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW || aTarget == nsISelectionPrivate::TABLESELECTION_COLUMN) |
|
2224 { |
|
2225 #ifdef DEBUG_TABLE_SELECTION |
|
2226 printf("aTarget == %d\n", aTarget); |
|
2227 #endif |
|
2228 |
|
2229 // Start drag-selecting mode so multiple rows/cols can be selected |
|
2230 // Note: Currently, nsFrame::GetDataForTableSelection |
|
2231 // will never call us for row or column selection on mouse down |
|
2232 mDragSelectingCells = true; |
|
2233 |
|
2234 // Force new selection block |
|
2235 mStartSelectedCell = nullptr; |
|
2236 mDomSelections[index]->RemoveAllRanges(); |
|
2237 // Always do this AFTER RemoveAllRanges |
|
2238 mSelectingTableCellMode = aTarget; |
|
2239 return SelectRowOrColumn(childContent, aTarget); |
|
2240 } |
|
2241 } |
|
2242 else |
|
2243 { |
|
2244 #ifdef DEBUG_TABLE_SELECTION |
|
2245 printf("HandleTableSelection: Mouse UP event. mDragSelectingCells=%d, mStartSelectedCell=%d\n", mDragSelectingCells, mStartSelectedCell); |
|
2246 #endif |
|
2247 // First check if we are extending a block selection |
|
2248 int32_t rangeCount; |
|
2249 result = mDomSelections[index]->GetRangeCount(&rangeCount); |
|
2250 if (NS_FAILED(result)) |
|
2251 return result; |
|
2252 |
|
2253 if (rangeCount > 0 && aMouseEvent->IsShift() && |
|
2254 mAppendStartSelectedCell && mAppendStartSelectedCell != childContent) |
|
2255 { |
|
2256 // Shift key is down: append a block selection |
|
2257 mDragSelectingCells = false; |
|
2258 return SelectBlockOfCells(mAppendStartSelectedCell, childContent); |
|
2259 } |
|
2260 |
|
2261 if (mDragSelectingCells) |
|
2262 mAppendStartSelectedCell = mStartSelectedCell; |
|
2263 |
|
2264 mDragSelectingCells = false; |
|
2265 mStartSelectedCell = nullptr; |
|
2266 mEndSelectedCell = nullptr; |
|
2267 |
|
2268 // Any other mouseup actions require that Ctrl or Cmd key is pressed |
|
2269 // else stop table selection mode |
|
2270 bool doMouseUpAction = false; |
|
2271 #ifdef XP_MACOSX |
|
2272 doMouseUpAction = aMouseEvent->IsMeta(); |
|
2273 #else |
|
2274 doMouseUpAction = aMouseEvent->IsControl(); |
|
2275 #endif |
|
2276 if (!doMouseUpAction) |
|
2277 { |
|
2278 #ifdef DEBUG_TABLE_SELECTION |
|
2279 printf("HandleTableSelection: Ending cell selection on mouseup: mAppendStartSelectedCell=%d\n", mAppendStartSelectedCell); |
|
2280 #endif |
|
2281 return NS_OK; |
|
2282 } |
|
2283 // Unselect a cell only if it wasn't |
|
2284 // just selected on mousedown |
|
2285 if( childContent == mUnselectCellOnMouseUp) |
|
2286 { |
|
2287 // Scan ranges to find the cell to unselect (the selection range to remove) |
|
2288 // XXXbz it's really weird that this lives outside the loop, so once we |
|
2289 // find one we keep looking at it even if we find no more cells... |
|
2290 nsINode* previousCellParent = nullptr; |
|
2291 #ifdef DEBUG_TABLE_SELECTION |
|
2292 printf("HandleTableSelection: Unselecting mUnselectCellOnMouseUp; rangeCount=%d\n", rangeCount); |
|
2293 #endif |
|
2294 for( int32_t i = 0; i < rangeCount; i++) |
|
2295 { |
|
2296 // Strong reference, because sometimes we want to remove |
|
2297 // this range, and then we might be the only owner. |
|
2298 nsRefPtr<nsRange> range = mDomSelections[index]->GetRangeAt(i); |
|
2299 if (!range) return NS_ERROR_NULL_POINTER; |
|
2300 |
|
2301 nsINode* parent = range->GetStartParent(); |
|
2302 if (!parent) return NS_ERROR_NULL_POINTER; |
|
2303 |
|
2304 int32_t offset = range->StartOffset(); |
|
2305 // Be sure previous selection is a table cell |
|
2306 nsIContent* child = parent->GetChildAt(offset); |
|
2307 if (child && IsCell(child)) |
|
2308 previousCellParent = parent; |
|
2309 |
|
2310 // We're done if we didn't find parent of a previously-selected cell |
|
2311 if (!previousCellParent) break; |
|
2312 |
|
2313 if (previousCellParent == aParentContent && offset == aContentOffset) |
|
2314 { |
|
2315 // Cell is already selected |
|
2316 if (rangeCount == 1) |
|
2317 { |
|
2318 #ifdef DEBUG_TABLE_SELECTION |
|
2319 printf("HandleTableSelection: Unselecting single selected cell\n"); |
|
2320 #endif |
|
2321 // This was the only cell selected. |
|
2322 // Collapse to "normal" selection inside the cell |
|
2323 mStartSelectedCell = nullptr; |
|
2324 mEndSelectedCell = nullptr; |
|
2325 mAppendStartSelectedCell = nullptr; |
|
2326 //TODO: We need a "Collapse to just before deepest child" routine |
|
2327 // Even better, should we collapse to just after the LAST deepest child |
|
2328 // (i.e., at the end of the cell's contents)? |
|
2329 return mDomSelections[index]->Collapse(childContent, 0); |
|
2330 } |
|
2331 #ifdef DEBUG_TABLE_SELECTION |
|
2332 printf("HandleTableSelection: Removing cell from multi-cell selection\n"); |
|
2333 #endif |
|
2334 // Unselecting the start of previous block |
|
2335 // XXX What do we use now! |
|
2336 if (childContent == mAppendStartSelectedCell) |
|
2337 mAppendStartSelectedCell = nullptr; |
|
2338 |
|
2339 // Deselect cell by removing its range from selection |
|
2340 return mDomSelections[index]->RemoveRange(range); |
|
2341 } |
|
2342 } |
|
2343 mUnselectCellOnMouseUp = nullptr; |
|
2344 } |
|
2345 } |
|
2346 } |
|
2347 return result; |
|
2348 } |
|
2349 |
|
2350 nsresult |
|
2351 nsFrameSelection::SelectBlockOfCells(nsIContent *aStartCell, nsIContent *aEndCell) |
|
2352 { |
|
2353 NS_ENSURE_TRUE(aStartCell, NS_ERROR_NULL_POINTER); |
|
2354 NS_ENSURE_TRUE(aEndCell, NS_ERROR_NULL_POINTER); |
|
2355 mEndSelectedCell = aEndCell; |
|
2356 |
|
2357 nsCOMPtr<nsIContent> startCell; |
|
2358 nsresult result = NS_OK; |
|
2359 |
|
2360 // If new end cell is in a different table, do nothing |
|
2361 nsIContent* table = IsInSameTable(aStartCell, aEndCell); |
|
2362 if (!table) { |
|
2363 return NS_OK; |
|
2364 } |
|
2365 |
|
2366 // Get starting and ending cells' location in the cellmap |
|
2367 int32_t startRowIndex, startColIndex, endRowIndex, endColIndex; |
|
2368 result = GetCellIndexes(aStartCell, startRowIndex, startColIndex); |
|
2369 if(NS_FAILED(result)) return result; |
|
2370 result = GetCellIndexes(aEndCell, endRowIndex, endColIndex); |
|
2371 if(NS_FAILED(result)) return result; |
|
2372 |
|
2373 if (mDragSelectingCells) |
|
2374 { |
|
2375 // Drag selecting: remove selected cells outside of new block limits |
|
2376 UnselectCells(table, startRowIndex, startColIndex, endRowIndex, endColIndex, |
|
2377 true); |
|
2378 } |
|
2379 |
|
2380 // Note that we select block in the direction of user's mouse dragging, |
|
2381 // which means start cell may be after the end cell in either row or column |
|
2382 return AddCellsToSelection(table, startRowIndex, startColIndex, |
|
2383 endRowIndex, endColIndex); |
|
2384 } |
|
2385 |
|
2386 nsresult |
|
2387 nsFrameSelection::UnselectCells(nsIContent *aTableContent, |
|
2388 int32_t aStartRowIndex, |
|
2389 int32_t aStartColumnIndex, |
|
2390 int32_t aEndRowIndex, |
|
2391 int32_t aEndColumnIndex, |
|
2392 bool aRemoveOutsideOfCellRange) |
|
2393 { |
|
2394 int8_t index = |
|
2395 GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
2396 if (!mDomSelections[index]) |
|
2397 return NS_ERROR_NULL_POINTER; |
|
2398 |
|
2399 nsTableOuterFrame* tableFrame = do_QueryFrame(aTableContent->GetPrimaryFrame()); |
|
2400 if (!tableFrame) |
|
2401 return NS_ERROR_FAILURE; |
|
2402 |
|
2403 int32_t minRowIndex = std::min(aStartRowIndex, aEndRowIndex); |
|
2404 int32_t maxRowIndex = std::max(aStartRowIndex, aEndRowIndex); |
|
2405 int32_t minColIndex = std::min(aStartColumnIndex, aEndColumnIndex); |
|
2406 int32_t maxColIndex = std::max(aStartColumnIndex, aEndColumnIndex); |
|
2407 |
|
2408 // Strong reference because we sometimes remove the range |
|
2409 nsRefPtr<nsRange> range = GetFirstCellRange(); |
|
2410 nsIContent* cellNode = GetFirstSelectedContent(range); |
|
2411 NS_PRECONDITION(!range || cellNode, "Must have cellNode if had a range"); |
|
2412 |
|
2413 int32_t curRowIndex, curColIndex; |
|
2414 while (cellNode) |
|
2415 { |
|
2416 nsresult result = GetCellIndexes(cellNode, curRowIndex, curColIndex); |
|
2417 if (NS_FAILED(result)) |
|
2418 return result; |
|
2419 |
|
2420 #ifdef DEBUG_TABLE_SELECTION |
|
2421 if (!range) |
|
2422 printf("RemoveCellsToSelection -- range is null\n"); |
|
2423 #endif |
|
2424 |
|
2425 if (range) { |
|
2426 if (aRemoveOutsideOfCellRange) { |
|
2427 if (curRowIndex < minRowIndex || curRowIndex > maxRowIndex || |
|
2428 curColIndex < minColIndex || curColIndex > maxColIndex) { |
|
2429 |
|
2430 mDomSelections[index]->RemoveRange(range); |
|
2431 // Since we've removed the range, decrement pointer to next range |
|
2432 mSelectedCellIndex--; |
|
2433 } |
|
2434 |
|
2435 } else { |
|
2436 // Remove cell from selection if it belongs to the given cells range or |
|
2437 // it is spanned onto the cells range. |
|
2438 nsTableCellFrame* cellFrame = |
|
2439 tableFrame->GetCellFrameAt(curRowIndex, curColIndex); |
|
2440 |
|
2441 int32_t origRowIndex, origColIndex; |
|
2442 cellFrame->GetRowIndex(origRowIndex); |
|
2443 cellFrame->GetColIndex(origColIndex); |
|
2444 uint32_t actualRowSpan = |
|
2445 tableFrame->GetEffectiveRowSpanAt(origRowIndex, origColIndex); |
|
2446 uint32_t actualColSpan = |
|
2447 tableFrame->GetEffectiveColSpanAt(curRowIndex, curColIndex); |
|
2448 if (origRowIndex <= maxRowIndex && maxRowIndex >= 0 && |
|
2449 origRowIndex + actualRowSpan - 1 >= static_cast<uint32_t>(minRowIndex) && |
|
2450 origColIndex <= maxColIndex && maxColIndex >= 0 && |
|
2451 origColIndex + actualColSpan - 1 >= static_cast<uint32_t>(minColIndex)) { |
|
2452 |
|
2453 mDomSelections[index]->RemoveRange(range); |
|
2454 // Since we've removed the range, decrement pointer to next range |
|
2455 mSelectedCellIndex--; |
|
2456 } |
|
2457 } |
|
2458 } |
|
2459 |
|
2460 range = GetNextCellRange(); |
|
2461 cellNode = GetFirstSelectedContent(range); |
|
2462 NS_PRECONDITION(!range || cellNode, "Must have cellNode if had a range"); |
|
2463 } |
|
2464 |
|
2465 return NS_OK; |
|
2466 } |
|
2467 |
|
2468 nsresult |
|
2469 nsFrameSelection::AddCellsToSelection(nsIContent *aTableContent, |
|
2470 int32_t aStartRowIndex, |
|
2471 int32_t aStartColumnIndex, |
|
2472 int32_t aEndRowIndex, |
|
2473 int32_t aEndColumnIndex) |
|
2474 { |
|
2475 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
2476 if (!mDomSelections[index]) |
|
2477 return NS_ERROR_NULL_POINTER; |
|
2478 |
|
2479 nsTableOuterFrame* tableFrame = do_QueryFrame(aTableContent->GetPrimaryFrame()); |
|
2480 if (!tableFrame) // Check that |table| is a table. |
|
2481 return NS_ERROR_FAILURE; |
|
2482 |
|
2483 nsresult result = NS_OK; |
|
2484 int32_t row = aStartRowIndex; |
|
2485 while(true) |
|
2486 { |
|
2487 int32_t col = aStartColumnIndex; |
|
2488 while(true) |
|
2489 { |
|
2490 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(row, col); |
|
2491 |
|
2492 // Skip cells that are spanned from previous locations or are already selected |
|
2493 if (cellFrame) { |
|
2494 int32_t origRow, origCol; |
|
2495 cellFrame->GetRowIndex(origRow); |
|
2496 cellFrame->GetColIndex(origCol); |
|
2497 if (origRow == row && origCol == col && !cellFrame->IsSelected()) { |
|
2498 result = SelectCellElement(cellFrame->GetContent()); |
|
2499 if (NS_FAILED(result)) return result; |
|
2500 } |
|
2501 } |
|
2502 // Done when we reach end column |
|
2503 if (col == aEndColumnIndex) break; |
|
2504 |
|
2505 if (aStartColumnIndex < aEndColumnIndex) |
|
2506 col ++; |
|
2507 else |
|
2508 col--; |
|
2509 }; |
|
2510 if (row == aEndRowIndex) break; |
|
2511 |
|
2512 if (aStartRowIndex < aEndRowIndex) |
|
2513 row++; |
|
2514 else |
|
2515 row--; |
|
2516 }; |
|
2517 return result; |
|
2518 } |
|
2519 |
|
2520 nsresult |
|
2521 nsFrameSelection::RemoveCellsFromSelection(nsIContent *aTable, |
|
2522 int32_t aStartRowIndex, |
|
2523 int32_t aStartColumnIndex, |
|
2524 int32_t aEndRowIndex, |
|
2525 int32_t aEndColumnIndex) |
|
2526 { |
|
2527 return UnselectCells(aTable, aStartRowIndex, aStartColumnIndex, |
|
2528 aEndRowIndex, aEndColumnIndex, false); |
|
2529 } |
|
2530 |
|
2531 nsresult |
|
2532 nsFrameSelection::RestrictCellsToSelection(nsIContent *aTable, |
|
2533 int32_t aStartRowIndex, |
|
2534 int32_t aStartColumnIndex, |
|
2535 int32_t aEndRowIndex, |
|
2536 int32_t aEndColumnIndex) |
|
2537 { |
|
2538 return UnselectCells(aTable, aStartRowIndex, aStartColumnIndex, |
|
2539 aEndRowIndex, aEndColumnIndex, true); |
|
2540 } |
|
2541 |
|
2542 nsresult |
|
2543 nsFrameSelection::SelectRowOrColumn(nsIContent *aCellContent, uint32_t aTarget) |
|
2544 { |
|
2545 if (!aCellContent) return NS_ERROR_NULL_POINTER; |
|
2546 |
|
2547 nsIContent* table = GetParentTable(aCellContent); |
|
2548 if (!table) return NS_ERROR_NULL_POINTER; |
|
2549 |
|
2550 // Get table and cell layout interfaces to access |
|
2551 // cell data based on cellmap location |
|
2552 // Frames are not ref counted, so don't use an nsCOMPtr |
|
2553 nsTableOuterFrame* tableFrame = do_QueryFrame(table->GetPrimaryFrame()); |
|
2554 if (!tableFrame) return NS_ERROR_FAILURE; |
|
2555 nsITableCellLayout *cellLayout = GetCellLayout(aCellContent); |
|
2556 if (!cellLayout) return NS_ERROR_FAILURE; |
|
2557 |
|
2558 // Get location of target cell: |
|
2559 int32_t rowIndex, colIndex; |
|
2560 nsresult result = cellLayout->GetCellIndexes(rowIndex, colIndex); |
|
2561 if (NS_FAILED(result)) return result; |
|
2562 |
|
2563 // Be sure we start at proper beginning |
|
2564 // (This allows us to select row or col given ANY cell!) |
|
2565 if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW) |
|
2566 colIndex = 0; |
|
2567 if (aTarget == nsISelectionPrivate::TABLESELECTION_COLUMN) |
|
2568 rowIndex = 0; |
|
2569 |
|
2570 nsCOMPtr<nsIContent> firstCell, lastCell; |
|
2571 while (true) { |
|
2572 // Loop through all cells in column or row to find first and last |
|
2573 nsCOMPtr<nsIContent> curCellContent = |
|
2574 tableFrame->GetCellAt(rowIndex, colIndex); |
|
2575 if (!curCellContent) |
|
2576 break; |
|
2577 |
|
2578 if (!firstCell) |
|
2579 firstCell = curCellContent; |
|
2580 |
|
2581 lastCell = curCellContent.forget(); |
|
2582 |
|
2583 // Move to next cell in cellmap, skipping spanned locations |
|
2584 if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW) |
|
2585 colIndex += tableFrame->GetEffectiveRowSpanAt(rowIndex, colIndex); |
|
2586 else |
|
2587 rowIndex += tableFrame->GetEffectiveRowSpanAt(rowIndex, colIndex); |
|
2588 } |
|
2589 |
|
2590 // Use SelectBlockOfCells: |
|
2591 // This will replace existing selection, |
|
2592 // but allow unselecting by dragging out of selected region |
|
2593 if (firstCell && lastCell) |
|
2594 { |
|
2595 if (!mStartSelectedCell) |
|
2596 { |
|
2597 // We are starting a new block, so select the first cell |
|
2598 result = SelectCellElement(firstCell); |
|
2599 if (NS_FAILED(result)) return result; |
|
2600 mStartSelectedCell = firstCell; |
|
2601 } |
|
2602 nsCOMPtr<nsIContent> lastCellContent = do_QueryInterface(lastCell); |
|
2603 result = SelectBlockOfCells(mStartSelectedCell, lastCellContent); |
|
2604 |
|
2605 // This gets set to the cell at end of row/col, |
|
2606 // but we need it to be the cell under cursor |
|
2607 mEndSelectedCell = aCellContent; |
|
2608 return result; |
|
2609 } |
|
2610 |
|
2611 #if 0 |
|
2612 // This is a more efficient strategy that appends row to current selection, |
|
2613 // but doesn't allow dragging OFF of an existing selection to unselect! |
|
2614 do { |
|
2615 // Loop through all cells in column or row |
|
2616 result = tableLayout->GetCellDataAt(rowIndex, colIndex, |
|
2617 getter_AddRefs(cellElement), |
|
2618 curRowIndex, curColIndex, |
|
2619 rowSpan, colSpan, |
|
2620 actualRowSpan, actualColSpan, |
|
2621 isSelected); |
|
2622 if (NS_FAILED(result)) return result; |
|
2623 // We're done when cell is not found |
|
2624 if (!cellElement) break; |
|
2625 |
|
2626 |
|
2627 // Check spans else we infinitely loop |
|
2628 NS_ASSERTION(actualColSpan, "actualColSpan is 0!"); |
|
2629 NS_ASSERTION(actualRowSpan, "actualRowSpan is 0!"); |
|
2630 |
|
2631 // Skip cells that are already selected or span from outside our region |
|
2632 if (!isSelected && rowIndex == curRowIndex && colIndex == curColIndex) |
|
2633 { |
|
2634 result = SelectCellElement(cellElement); |
|
2635 if (NS_FAILED(result)) return result; |
|
2636 } |
|
2637 // Move to next row or column in cellmap, skipping spanned locations |
|
2638 if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW) |
|
2639 colIndex += actualColSpan; |
|
2640 else |
|
2641 rowIndex += actualRowSpan; |
|
2642 } |
|
2643 while (cellElement); |
|
2644 #endif |
|
2645 |
|
2646 return NS_OK; |
|
2647 } |
|
2648 |
|
2649 nsIContent* |
|
2650 nsFrameSelection::GetFirstCellNodeInRange(nsRange *aRange) const |
|
2651 { |
|
2652 if (!aRange) return nullptr; |
|
2653 |
|
2654 nsINode* startParent = aRange->GetStartParent(); |
|
2655 if (!startParent) |
|
2656 return nullptr; |
|
2657 |
|
2658 int32_t offset = aRange->StartOffset(); |
|
2659 |
|
2660 nsIContent* childContent = startParent->GetChildAt(offset); |
|
2661 if (!childContent) |
|
2662 return nullptr; |
|
2663 // Don't return node if not a cell |
|
2664 if (!IsCell(childContent)) |
|
2665 return nullptr; |
|
2666 |
|
2667 return childContent; |
|
2668 } |
|
2669 |
|
2670 nsRange* |
|
2671 nsFrameSelection::GetFirstCellRange() |
|
2672 { |
|
2673 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
2674 if (!mDomSelections[index]) |
|
2675 return nullptr; |
|
2676 |
|
2677 nsRange* firstRange = mDomSelections[index]->GetRangeAt(0); |
|
2678 if (!GetFirstCellNodeInRange(firstRange)) { |
|
2679 return nullptr; |
|
2680 } |
|
2681 |
|
2682 // Setup for next cell |
|
2683 mSelectedCellIndex = 1; |
|
2684 |
|
2685 return firstRange; |
|
2686 } |
|
2687 |
|
2688 nsRange* |
|
2689 nsFrameSelection::GetNextCellRange() |
|
2690 { |
|
2691 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
2692 if (!mDomSelections[index]) |
|
2693 return nullptr; |
|
2694 |
|
2695 nsRange* range = mDomSelections[index]->GetRangeAt(mSelectedCellIndex); |
|
2696 |
|
2697 // Get first node in next range of selection - test if it's a cell |
|
2698 if (!GetFirstCellNodeInRange(range)) { |
|
2699 return nullptr; |
|
2700 } |
|
2701 |
|
2702 // Setup for next cell |
|
2703 mSelectedCellIndex++; |
|
2704 |
|
2705 return range; |
|
2706 } |
|
2707 |
|
2708 nsresult |
|
2709 nsFrameSelection::GetCellIndexes(nsIContent *aCell, |
|
2710 int32_t &aRowIndex, |
|
2711 int32_t &aColIndex) |
|
2712 { |
|
2713 if (!aCell) return NS_ERROR_NULL_POINTER; |
|
2714 |
|
2715 aColIndex=0; // initialize out params |
|
2716 aRowIndex=0; |
|
2717 |
|
2718 nsITableCellLayout *cellLayoutObject = GetCellLayout(aCell); |
|
2719 if (!cellLayoutObject) return NS_ERROR_FAILURE; |
|
2720 return cellLayoutObject->GetCellIndexes(aRowIndex, aColIndex); |
|
2721 } |
|
2722 |
|
2723 nsIContent* |
|
2724 nsFrameSelection::IsInSameTable(nsIContent *aContent1, |
|
2725 nsIContent *aContent2) const |
|
2726 { |
|
2727 if (!aContent1 || !aContent2) return nullptr; |
|
2728 |
|
2729 nsIContent* tableNode1 = GetParentTable(aContent1); |
|
2730 nsIContent* tableNode2 = GetParentTable(aContent2); |
|
2731 |
|
2732 // Must be in the same table. Note that we want to return false for |
|
2733 // the test if both tables are null. |
|
2734 return (tableNode1 == tableNode2) ? tableNode1 : nullptr; |
|
2735 } |
|
2736 |
|
2737 nsIContent* |
|
2738 nsFrameSelection::GetParentTable(nsIContent *aCell) const |
|
2739 { |
|
2740 if (!aCell) { |
|
2741 return nullptr; |
|
2742 } |
|
2743 |
|
2744 for (nsIContent* parent = aCell->GetParent(); parent; |
|
2745 parent = parent->GetParent()) { |
|
2746 if (parent->Tag() == nsGkAtoms::table && |
|
2747 parent->IsHTML()) { |
|
2748 return parent; |
|
2749 } |
|
2750 } |
|
2751 |
|
2752 return nullptr; |
|
2753 } |
|
2754 |
|
2755 nsresult |
|
2756 nsFrameSelection::SelectCellElement(nsIContent *aCellElement) |
|
2757 { |
|
2758 nsIContent *parent = aCellElement->GetParent(); |
|
2759 |
|
2760 // Get child offset |
|
2761 int32_t offset = parent->IndexOf(aCellElement); |
|
2762 |
|
2763 return CreateAndAddRange(parent, offset); |
|
2764 } |
|
2765 |
|
2766 nsresult |
|
2767 Selection::getTableCellLocationFromRange(nsRange* aRange, |
|
2768 int32_t* aSelectionType, |
|
2769 int32_t* aRow, int32_t* aCol) |
|
2770 { |
|
2771 if (!aRange || !aSelectionType || !aRow || !aCol) |
|
2772 return NS_ERROR_NULL_POINTER; |
|
2773 |
|
2774 *aSelectionType = nsISelectionPrivate::TABLESELECTION_NONE; |
|
2775 *aRow = 0; |
|
2776 *aCol = 0; |
|
2777 |
|
2778 // Must have access to frame selection to get cell info |
|
2779 if (!mFrameSelection) return NS_OK; |
|
2780 |
|
2781 nsresult result = GetTableSelectionType(aRange, aSelectionType); |
|
2782 if (NS_FAILED(result)) return result; |
|
2783 |
|
2784 // Don't fail if range does not point to a single table cell, |
|
2785 // let aSelectionType tell user if we don't have a cell |
|
2786 if (*aSelectionType != nsISelectionPrivate::TABLESELECTION_CELL) |
|
2787 return NS_OK; |
|
2788 |
|
2789 // Get the child content (the cell) pointed to by starting node of range |
|
2790 // We do minimal checking since GetTableSelectionType assures |
|
2791 // us that this really is a table cell |
|
2792 nsCOMPtr<nsIContent> content = do_QueryInterface(aRange->GetStartParent()); |
|
2793 if (!content) |
|
2794 return NS_ERROR_FAILURE; |
|
2795 |
|
2796 nsIContent *child = content->GetChildAt(aRange->StartOffset()); |
|
2797 if (!child) |
|
2798 return NS_ERROR_FAILURE; |
|
2799 |
|
2800 //Note: This is a non-ref-counted pointer to the frame |
|
2801 nsITableCellLayout *cellLayout = mFrameSelection->GetCellLayout(child); |
|
2802 if (NS_FAILED(result)) |
|
2803 return result; |
|
2804 if (!cellLayout) |
|
2805 return NS_ERROR_FAILURE; |
|
2806 |
|
2807 return cellLayout->GetCellIndexes(*aRow, *aCol); |
|
2808 } |
|
2809 |
|
2810 nsresult |
|
2811 Selection::addTableCellRange(nsRange* aRange, bool* aDidAddRange, |
|
2812 int32_t* aOutIndex) |
|
2813 { |
|
2814 if (!aDidAddRange || !aOutIndex) |
|
2815 return NS_ERROR_NULL_POINTER; |
|
2816 |
|
2817 *aDidAddRange = false; |
|
2818 *aOutIndex = -1; |
|
2819 |
|
2820 if (!mFrameSelection) |
|
2821 return NS_OK; |
|
2822 |
|
2823 if (!aRange) |
|
2824 return NS_ERROR_NULL_POINTER; |
|
2825 |
|
2826 nsresult result; |
|
2827 |
|
2828 // Get if we are adding a cell selection and the row, col of cell if we are |
|
2829 int32_t newRow, newCol, tableMode; |
|
2830 result = getTableCellLocationFromRange(aRange, &tableMode, &newRow, &newCol); |
|
2831 if (NS_FAILED(result)) return result; |
|
2832 |
|
2833 // If not adding a cell range, we are done here |
|
2834 if (tableMode != nsISelectionPrivate::TABLESELECTION_CELL) |
|
2835 { |
|
2836 mFrameSelection->mSelectingTableCellMode = tableMode; |
|
2837 // Don't fail if range isn't a selected cell, aDidAddRange tells caller if we didn't proceed |
|
2838 return NS_OK; |
|
2839 } |
|
2840 |
|
2841 // Set frame selection mode only if not already set to a table mode |
|
2842 // so we don't lose the select row and column flags (not detected by getTableCellLocation) |
|
2843 if (mFrameSelection->mSelectingTableCellMode == TABLESELECTION_NONE) |
|
2844 mFrameSelection->mSelectingTableCellMode = tableMode; |
|
2845 |
|
2846 *aDidAddRange = true; |
|
2847 return AddItem(aRange, aOutIndex); |
|
2848 } |
|
2849 |
|
2850 //TODO: Figure out TABLESELECTION_COLUMN and TABLESELECTION_ALLCELLS |
|
2851 nsresult |
|
2852 Selection::GetTableSelectionType(nsIDOMRange* aDOMRange, |
|
2853 int32_t* aTableSelectionType) |
|
2854 { |
|
2855 if (!aDOMRange || !aTableSelectionType) |
|
2856 return NS_ERROR_NULL_POINTER; |
|
2857 nsRange* range = static_cast<nsRange*>(aDOMRange); |
|
2858 |
|
2859 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_NONE; |
|
2860 |
|
2861 // Must have access to frame selection to get cell info |
|
2862 if(!mFrameSelection) return NS_OK; |
|
2863 |
|
2864 nsINode* startNode = range->GetStartParent(); |
|
2865 if (!startNode) return NS_ERROR_FAILURE; |
|
2866 |
|
2867 nsINode* endNode = range->GetEndParent(); |
|
2868 if (!endNode) return NS_ERROR_FAILURE; |
|
2869 |
|
2870 // Not a single selected node |
|
2871 if (startNode != endNode) return NS_OK; |
|
2872 |
|
2873 int32_t startOffset = range->StartOffset(); |
|
2874 int32_t endOffset = range->EndOffset(); |
|
2875 |
|
2876 // Not a single selected node |
|
2877 if ((endOffset - startOffset) != 1) |
|
2878 return NS_OK; |
|
2879 |
|
2880 nsIContent* startContent = static_cast<nsIContent*>(startNode); |
|
2881 if (!(startNode->IsElement() && startContent->IsHTML())) { |
|
2882 // Implies a check for being an element; if we ever make this work |
|
2883 // for non-HTML, need to keep checking for elements. |
|
2884 return NS_OK; |
|
2885 } |
|
2886 |
|
2887 nsIAtom *tag = startContent->Tag(); |
|
2888 |
|
2889 if (tag == nsGkAtoms::tr) |
|
2890 { |
|
2891 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_CELL; |
|
2892 } |
|
2893 else //check to see if we are selecting a table or row (column and all cells not done yet) |
|
2894 { |
|
2895 nsIContent *child = startNode->GetChildAt(startOffset); |
|
2896 if (!child) |
|
2897 return NS_ERROR_FAILURE; |
|
2898 |
|
2899 tag = child->Tag(); |
|
2900 |
|
2901 if (tag == nsGkAtoms::table) |
|
2902 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_TABLE; |
|
2903 else if (tag == nsGkAtoms::tr) |
|
2904 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_ROW; |
|
2905 } |
|
2906 |
|
2907 return NS_OK; |
|
2908 } |
|
2909 |
|
2910 nsresult |
|
2911 nsFrameSelection::CreateAndAddRange(nsINode *aParentNode, int32_t aOffset) |
|
2912 { |
|
2913 if (!aParentNode) return NS_ERROR_NULL_POINTER; |
|
2914 |
|
2915 nsRefPtr<nsRange> range = new nsRange(aParentNode); |
|
2916 |
|
2917 // Set range around child at given offset |
|
2918 nsresult result = range->SetStart(aParentNode, aOffset); |
|
2919 if (NS_FAILED(result)) return result; |
|
2920 result = range->SetEnd(aParentNode, aOffset+1); |
|
2921 if (NS_FAILED(result)) return result; |
|
2922 |
|
2923 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
2924 if (!mDomSelections[index]) |
|
2925 return NS_ERROR_NULL_POINTER; |
|
2926 |
|
2927 return mDomSelections[index]->AddRange(range); |
|
2928 } |
|
2929 |
|
2930 // End of Table Selection |
|
2931 |
|
2932 void |
|
2933 nsFrameSelection::SetAncestorLimiter(nsIContent *aLimiter) |
|
2934 { |
|
2935 if (mAncestorLimiter != aLimiter) { |
|
2936 mAncestorLimiter = aLimiter; |
|
2937 int8_t index = |
|
2938 GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
2939 if (!mDomSelections[index]) |
|
2940 return; |
|
2941 |
|
2942 if (!IsValidSelectionPoint(this, mDomSelections[index]->GetFocusNode())) { |
|
2943 ClearNormalSelection(); |
|
2944 if (mAncestorLimiter) { |
|
2945 PostReason(nsISelectionListener::NO_REASON); |
|
2946 TakeFocus(mAncestorLimiter, 0, 0, HINTLEFT, false, false); |
|
2947 } |
|
2948 } |
|
2949 } |
|
2950 } |
|
2951 |
|
2952 //END nsFrameSelection methods |
|
2953 |
|
2954 |
|
2955 //BEGIN nsISelection interface implementations |
|
2956 |
|
2957 |
|
2958 |
|
2959 nsresult |
|
2960 nsFrameSelection::DeleteFromDocument() |
|
2961 { |
|
2962 nsresult res; |
|
2963 |
|
2964 // If we're already collapsed, then we do nothing (bug 719503). |
|
2965 bool isCollapsed; |
|
2966 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); |
|
2967 if (!mDomSelections[index]) |
|
2968 return NS_ERROR_NULL_POINTER; |
|
2969 |
|
2970 mDomSelections[index]->GetIsCollapsed( &isCollapsed); |
|
2971 if (isCollapsed) |
|
2972 { |
|
2973 return NS_OK; |
|
2974 } |
|
2975 |
|
2976 nsRefPtr<Selection> selection = mDomSelections[index]; |
|
2977 for (int32_t rangeIdx = 0; rangeIdx < selection->GetRangeCount(); ++rangeIdx) { |
|
2978 nsRefPtr<nsRange> range = selection->GetRangeAt(rangeIdx); |
|
2979 res = range->DeleteContents(); |
|
2980 if (NS_FAILED(res)) |
|
2981 return res; |
|
2982 } |
|
2983 |
|
2984 // Collapse to the new location. |
|
2985 // If we deleted one character, then we move back one element. |
|
2986 // FIXME We don't know how to do this past frame boundaries yet. |
|
2987 if (isCollapsed) |
|
2988 mDomSelections[index]->Collapse(mDomSelections[index]->GetAnchorNode(), mDomSelections[index]->AnchorOffset()-1); |
|
2989 else if (mDomSelections[index]->AnchorOffset() > 0) |
|
2990 mDomSelections[index]->Collapse(mDomSelections[index]->GetAnchorNode(), mDomSelections[index]->AnchorOffset()); |
|
2991 #ifdef DEBUG |
|
2992 else |
|
2993 printf("Don't know how to set selection back past frame boundary\n"); |
|
2994 #endif |
|
2995 |
|
2996 return NS_OK; |
|
2997 } |
|
2998 |
|
2999 void |
|
3000 nsFrameSelection::SetDelayedCaretData(WidgetMouseEvent* aMouseEvent) |
|
3001 { |
|
3002 if (aMouseEvent) { |
|
3003 mDelayedMouseEventValid = true; |
|
3004 mDelayedMouseEventIsShift = aMouseEvent->IsShift(); |
|
3005 mDelayedMouseEventClickCount = aMouseEvent->clickCount; |
|
3006 } else { |
|
3007 mDelayedMouseEventValid = false; |
|
3008 } |
|
3009 } |
|
3010 |
|
3011 void |
|
3012 nsFrameSelection::DisconnectFromPresShell() |
|
3013 { |
|
3014 StopAutoScrollTimer(); |
|
3015 for (int32_t i = 0; i < nsISelectionController::NUM_SELECTIONTYPES; i++) { |
|
3016 mDomSelections[i]->Clear(nullptr); |
|
3017 } |
|
3018 mShell = nullptr; |
|
3019 } |
|
3020 |
|
3021 //END nsISelection interface implementations |
|
3022 |
|
3023 #if 0 |
|
3024 #pragma mark - |
|
3025 #endif |
|
3026 |
|
3027 // mozilla::dom::Selection implementation |
|
3028 |
|
3029 // note: this can return a nil anchor node |
|
3030 |
|
3031 Selection::Selection() |
|
3032 : mCachedOffsetForFrame(nullptr) |
|
3033 , mDirection(eDirNext) |
|
3034 , mType(nsISelectionController::SELECTION_NORMAL) |
|
3035 { |
|
3036 SetIsDOMBinding(); |
|
3037 } |
|
3038 |
|
3039 Selection::Selection(nsFrameSelection* aList) |
|
3040 : mFrameSelection(aList) |
|
3041 , mCachedOffsetForFrame(nullptr) |
|
3042 , mDirection(eDirNext) |
|
3043 , mType(nsISelectionController::SELECTION_NORMAL) |
|
3044 { |
|
3045 SetIsDOMBinding(); |
|
3046 } |
|
3047 |
|
3048 Selection::~Selection() |
|
3049 { |
|
3050 setAnchorFocusRange(-1); |
|
3051 |
|
3052 uint32_t count = mRanges.Length(); |
|
3053 for (uint32_t i = 0; i < count; ++i) { |
|
3054 mRanges[i].mRange->SetInSelection(false); |
|
3055 } |
|
3056 |
|
3057 if (mAutoScrollTimer) { |
|
3058 mAutoScrollTimer->Stop(); |
|
3059 mAutoScrollTimer = nullptr; |
|
3060 } |
|
3061 |
|
3062 mScrollEvent.Revoke(); |
|
3063 |
|
3064 if (mCachedOffsetForFrame) { |
|
3065 delete mCachedOffsetForFrame; |
|
3066 mCachedOffsetForFrame = nullptr; |
|
3067 } |
|
3068 } |
|
3069 |
|
3070 nsIDocument* |
|
3071 Selection::GetParentObject() const |
|
3072 { |
|
3073 nsIPresShell* shell = GetPresShell(); |
|
3074 if (shell) { |
|
3075 return shell->GetDocument(); |
|
3076 } |
|
3077 return nullptr; |
|
3078 } |
|
3079 |
|
3080 NS_IMPL_CYCLE_COLLECTION_CLASS(Selection) |
|
3081 |
|
3082 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Selection) |
|
3083 // Unlink the selection listeners *before* we do RemoveAllRanges since |
|
3084 // we don't want to notify the listeners during JS GC (they could be |
|
3085 // in JS!). |
|
3086 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionListeners) |
|
3087 tmp->RemoveAllRanges(); |
|
3088 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameSelection) |
|
3089 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER |
|
3090 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
3091 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Selection) |
|
3092 { |
|
3093 uint32_t i, count = tmp->mRanges.Length(); |
|
3094 for (i = 0; i < count; ++i) { |
|
3095 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRanges[i].mRange) |
|
3096 } |
|
3097 } |
|
3098 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorFocusRange) |
|
3099 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameSelection) |
|
3100 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionListeners) |
|
3101 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS |
|
3102 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
3103 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Selection) |
|
3104 |
|
3105 DOMCI_DATA(Selection, Selection) |
|
3106 |
|
3107 // QueryInterface implementation for Selection |
|
3108 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Selection) |
|
3109 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY |
|
3110 NS_INTERFACE_MAP_ENTRY(nsISelection) |
|
3111 NS_INTERFACE_MAP_ENTRY(nsISelectionPrivate) |
|
3112 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) |
|
3113 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISelection) |
|
3114 NS_INTERFACE_MAP_END |
|
3115 |
|
3116 NS_IMPL_CYCLE_COLLECTING_ADDREF(Selection) |
|
3117 NS_IMPL_CYCLE_COLLECTING_RELEASE(Selection) |
|
3118 |
|
3119 |
|
3120 NS_IMETHODIMP |
|
3121 Selection::GetAnchorNode(nsIDOMNode** aAnchorNode) |
|
3122 { |
|
3123 nsINode* anchorNode = GetAnchorNode(); |
|
3124 if (anchorNode) { |
|
3125 return CallQueryInterface(anchorNode, aAnchorNode); |
|
3126 } |
|
3127 |
|
3128 *aAnchorNode = nullptr; |
|
3129 return NS_OK; |
|
3130 } |
|
3131 |
|
3132 nsINode* |
|
3133 Selection::GetAnchorNode() |
|
3134 { |
|
3135 if (!mAnchorFocusRange) |
|
3136 return nullptr; |
|
3137 |
|
3138 if (GetDirection() == eDirNext) { |
|
3139 return mAnchorFocusRange->GetStartParent(); |
|
3140 } |
|
3141 |
|
3142 return mAnchorFocusRange->GetEndParent(); |
|
3143 } |
|
3144 |
|
3145 NS_IMETHODIMP |
|
3146 Selection::GetAnchorOffset(int32_t* aAnchorOffset) |
|
3147 { |
|
3148 *aAnchorOffset = static_cast<int32_t>(AnchorOffset()); |
|
3149 return NS_OK; |
|
3150 } |
|
3151 |
|
3152 // note: this can return a nil focus node |
|
3153 NS_IMETHODIMP |
|
3154 Selection::GetFocusNode(nsIDOMNode** aFocusNode) |
|
3155 { |
|
3156 nsINode* focusNode = GetFocusNode(); |
|
3157 if (focusNode) { |
|
3158 return CallQueryInterface(focusNode, aFocusNode); |
|
3159 } |
|
3160 |
|
3161 *aFocusNode = nullptr; |
|
3162 return NS_OK; |
|
3163 } |
|
3164 |
|
3165 nsINode* |
|
3166 Selection::GetFocusNode() |
|
3167 { |
|
3168 if (!mAnchorFocusRange) |
|
3169 return nullptr; |
|
3170 |
|
3171 if (GetDirection() == eDirNext){ |
|
3172 return mAnchorFocusRange->GetEndParent(); |
|
3173 } |
|
3174 |
|
3175 return mAnchorFocusRange->GetStartParent(); |
|
3176 } |
|
3177 |
|
3178 NS_IMETHODIMP |
|
3179 Selection::GetFocusOffset(int32_t* aFocusOffset) |
|
3180 { |
|
3181 *aFocusOffset = static_cast<int32_t>(FocusOffset()); |
|
3182 return NS_OK; |
|
3183 } |
|
3184 |
|
3185 void |
|
3186 Selection::setAnchorFocusRange(int32_t indx) |
|
3187 { |
|
3188 if (indx >= (int32_t)mRanges.Length()) |
|
3189 return; |
|
3190 if (indx < 0) //release all |
|
3191 { |
|
3192 mAnchorFocusRange = nullptr; |
|
3193 } |
|
3194 else{ |
|
3195 mAnchorFocusRange = mRanges[indx].mRange; |
|
3196 } |
|
3197 } |
|
3198 |
|
3199 uint32_t |
|
3200 Selection::AnchorOffset() |
|
3201 { |
|
3202 if (!mAnchorFocusRange) |
|
3203 return 0; |
|
3204 |
|
3205 if (GetDirection() == eDirNext){ |
|
3206 return mAnchorFocusRange->StartOffset(); |
|
3207 } |
|
3208 |
|
3209 return mAnchorFocusRange->EndOffset(); |
|
3210 } |
|
3211 |
|
3212 uint32_t |
|
3213 Selection::FocusOffset() |
|
3214 { |
|
3215 if (!mAnchorFocusRange) |
|
3216 return 0; |
|
3217 |
|
3218 if (GetDirection() == eDirNext){ |
|
3219 return mAnchorFocusRange->EndOffset(); |
|
3220 } |
|
3221 |
|
3222 return mAnchorFocusRange->StartOffset(); |
|
3223 } |
|
3224 |
|
3225 static nsresult |
|
3226 CompareToRangeStart(nsINode* aCompareNode, int32_t aCompareOffset, |
|
3227 nsRange* aRange, int32_t* aCmp) |
|
3228 { |
|
3229 nsINode* start = aRange->GetStartParent(); |
|
3230 NS_ENSURE_STATE(aCompareNode && start); |
|
3231 // If the nodes that we're comparing are not in the same document, |
|
3232 // assume that aCompareNode will fall at the end of the ranges. |
|
3233 if (aCompareNode->GetCurrentDoc() != start->GetCurrentDoc() || |
|
3234 !start->GetCurrentDoc()) { |
|
3235 *aCmp = 1; |
|
3236 } else { |
|
3237 *aCmp = nsContentUtils::ComparePoints(aCompareNode, aCompareOffset, |
|
3238 start, aRange->StartOffset()); |
|
3239 } |
|
3240 return NS_OK; |
|
3241 } |
|
3242 |
|
3243 static nsresult |
|
3244 CompareToRangeEnd(nsINode* aCompareNode, int32_t aCompareOffset, |
|
3245 nsRange* aRange, int32_t* aCmp) |
|
3246 { |
|
3247 nsINode* end = aRange->GetEndParent(); |
|
3248 NS_ENSURE_STATE(aCompareNode && end); |
|
3249 // If the nodes that we're comparing are not in the same document, |
|
3250 // assume that aCompareNode will fall at the end of the ranges. |
|
3251 if (aCompareNode->GetCurrentDoc() != end->GetCurrentDoc() || |
|
3252 !end->GetCurrentDoc()) { |
|
3253 *aCmp = 1; |
|
3254 } else { |
|
3255 *aCmp = nsContentUtils::ComparePoints(aCompareNode, aCompareOffset, |
|
3256 end, aRange->EndOffset()); |
|
3257 } |
|
3258 return NS_OK; |
|
3259 } |
|
3260 |
|
3261 // Selection::FindInsertionPoint |
|
3262 // |
|
3263 // Binary searches the given sorted array of ranges for the insertion point |
|
3264 // for the given node/offset. The given comparator is used, and the index |
|
3265 // where the point should appear in the array is placed in *aInsertionPoint. |
|
3266 // |
|
3267 // If there is an item in the array equal to the input point, we will return |
|
3268 // the index of this item. |
|
3269 |
|
3270 nsresult |
|
3271 Selection::FindInsertionPoint( |
|
3272 nsTArray<RangeData>* aElementArray, |
|
3273 nsINode* aPointNode, int32_t aPointOffset, |
|
3274 nsresult (*aComparator)(nsINode*,int32_t,nsRange*,int32_t*), |
|
3275 int32_t* aPoint) |
|
3276 { |
|
3277 *aPoint = 0; |
|
3278 int32_t beginSearch = 0; |
|
3279 int32_t endSearch = aElementArray->Length(); // one beyond what to check |
|
3280 |
|
3281 if (endSearch) { |
|
3282 int32_t center = endSearch - 1; // Check last index, then binary search |
|
3283 do { |
|
3284 nsRange* range = (*aElementArray)[center].mRange; |
|
3285 |
|
3286 int32_t cmp; |
|
3287 nsresult rv = aComparator(aPointNode, aPointOffset, range, &cmp); |
|
3288 NS_ENSURE_SUCCESS(rv, rv); |
|
3289 |
|
3290 if (cmp < 0) { // point < cur |
|
3291 endSearch = center; |
|
3292 } else if (cmp > 0) { // point > cur |
|
3293 beginSearch = center + 1; |
|
3294 } else { // found match, done |
|
3295 beginSearch = center; |
|
3296 break; |
|
3297 } |
|
3298 center = (endSearch - beginSearch) / 2 + beginSearch; |
|
3299 } while (endSearch - beginSearch > 0); |
|
3300 } |
|
3301 |
|
3302 *aPoint = beginSearch; |
|
3303 return NS_OK; |
|
3304 } |
|
3305 |
|
3306 // Selection::SubtractRange |
|
3307 // |
|
3308 // A helper function that subtracts aSubtract from aRange, and adds |
|
3309 // 1 or 2 RangeData objects representing the remaining non-overlapping |
|
3310 // difference to aOutput. It is assumed that the caller has checked that |
|
3311 // aRange and aSubtract do indeed overlap |
|
3312 |
|
3313 nsresult |
|
3314 Selection::SubtractRange(RangeData* aRange, nsRange* aSubtract, |
|
3315 nsTArray<RangeData>* aOutput) |
|
3316 { |
|
3317 nsRange* range = aRange->mRange; |
|
3318 |
|
3319 // First we want to compare to the range start |
|
3320 int32_t cmp; |
|
3321 nsresult rv = CompareToRangeStart(range->GetStartParent(), |
|
3322 range->StartOffset(), |
|
3323 aSubtract, &cmp); |
|
3324 NS_ENSURE_SUCCESS(rv, rv); |
|
3325 |
|
3326 // Also, make a comparison to the range end |
|
3327 int32_t cmp2; |
|
3328 rv = CompareToRangeEnd(range->GetEndParent(), |
|
3329 range->EndOffset(), |
|
3330 aSubtract, &cmp2); |
|
3331 NS_ENSURE_SUCCESS(rv, rv); |
|
3332 |
|
3333 // If the existing range left overlaps the new range (aSubtract) then |
|
3334 // cmp < 0, and cmp2 < 0 |
|
3335 // If it right overlaps the new range then cmp > 0 and cmp2 > 0 |
|
3336 // If it fully contains the new range, then cmp < 0 and cmp2 > 0 |
|
3337 |
|
3338 if (cmp2 > 0) { |
|
3339 // We need to add a new RangeData to the output, running from |
|
3340 // the end of aSubtract to the end of range |
|
3341 nsRefPtr<nsRange> postOverlap = new nsRange(aSubtract->GetEndParent()); |
|
3342 |
|
3343 rv = |
|
3344 postOverlap->SetStart(aSubtract->GetEndParent(), aSubtract->EndOffset()); |
|
3345 NS_ENSURE_SUCCESS(rv, rv); |
|
3346 rv = |
|
3347 postOverlap->SetEnd(range->GetEndParent(), range->EndOffset()); |
|
3348 NS_ENSURE_SUCCESS(rv, rv); |
|
3349 if (!postOverlap->Collapsed()) { |
|
3350 if (!aOutput->InsertElementAt(0, RangeData(postOverlap))) |
|
3351 return NS_ERROR_OUT_OF_MEMORY; |
|
3352 (*aOutput)[0].mTextRangeStyle = aRange->mTextRangeStyle; |
|
3353 } |
|
3354 } |
|
3355 |
|
3356 if (cmp < 0) { |
|
3357 // We need to add a new RangeData to the output, running from |
|
3358 // the start of the range to the start of aSubtract |
|
3359 nsRefPtr<nsRange> preOverlap = new nsRange(range->GetStartParent()); |
|
3360 |
|
3361 nsresult rv = |
|
3362 preOverlap->SetStart(range->GetStartParent(), range->StartOffset()); |
|
3363 NS_ENSURE_SUCCESS(rv, rv); |
|
3364 rv = |
|
3365 preOverlap->SetEnd(aSubtract->GetStartParent(), aSubtract->StartOffset()); |
|
3366 NS_ENSURE_SUCCESS(rv, rv); |
|
3367 |
|
3368 if (!preOverlap->Collapsed()) { |
|
3369 if (!aOutput->InsertElementAt(0, RangeData(preOverlap))) |
|
3370 return NS_ERROR_OUT_OF_MEMORY; |
|
3371 (*aOutput)[0].mTextRangeStyle = aRange->mTextRangeStyle; |
|
3372 } |
|
3373 } |
|
3374 |
|
3375 return NS_OK; |
|
3376 } |
|
3377 |
|
3378 nsresult |
|
3379 Selection::AddItem(nsRange* aItem, int32_t* aOutIndex) |
|
3380 { |
|
3381 if (!aItem) |
|
3382 return NS_ERROR_NULL_POINTER; |
|
3383 if (!aItem->IsPositioned()) |
|
3384 return NS_ERROR_UNEXPECTED; |
|
3385 |
|
3386 NS_ASSERTION(aOutIndex, "aOutIndex can't be null"); |
|
3387 |
|
3388 *aOutIndex = -1; |
|
3389 |
|
3390 // a common case is that we have no ranges yet |
|
3391 if (mRanges.Length() == 0) { |
|
3392 if (!mRanges.AppendElement(RangeData(aItem))) |
|
3393 return NS_ERROR_OUT_OF_MEMORY; |
|
3394 aItem->SetInSelection(true); |
|
3395 |
|
3396 *aOutIndex = 0; |
|
3397 return NS_OK; |
|
3398 } |
|
3399 |
|
3400 int32_t startIndex, endIndex; |
|
3401 nsresult rv = GetIndicesForInterval(aItem->GetStartParent(), |
|
3402 aItem->StartOffset(), |
|
3403 aItem->GetEndParent(), |
|
3404 aItem->EndOffset(), false, |
|
3405 &startIndex, &endIndex); |
|
3406 NS_ENSURE_SUCCESS(rv, rv); |
|
3407 |
|
3408 if (endIndex == -1) { |
|
3409 // All ranges start after the given range. We can insert our range at |
|
3410 // position 0, knowing there are no overlaps (handled below) |
|
3411 startIndex = 0; |
|
3412 endIndex = 0; |
|
3413 } else if (startIndex == -1) { |
|
3414 // All ranges end before the given range. We can insert our range at |
|
3415 // the end of the array, knowing there are no overlaps (handled below) |
|
3416 startIndex = mRanges.Length(); |
|
3417 endIndex = startIndex; |
|
3418 } |
|
3419 |
|
3420 // If the range is already contained in mRanges, silently succeed |
|
3421 bool sameRange = EqualsRangeAtPoint(aItem->GetStartParent(), |
|
3422 aItem->StartOffset(), |
|
3423 aItem->GetEndParent(), |
|
3424 aItem->EndOffset(), startIndex); |
|
3425 if (sameRange) { |
|
3426 *aOutIndex = startIndex; |
|
3427 return NS_OK; |
|
3428 } |
|
3429 |
|
3430 if (startIndex == endIndex) { |
|
3431 // The new range doesn't overlap any existing ranges |
|
3432 if (!mRanges.InsertElementAt(startIndex, RangeData(aItem))) |
|
3433 return NS_ERROR_OUT_OF_MEMORY; |
|
3434 aItem->SetInSelection(true); |
|
3435 *aOutIndex = startIndex; |
|
3436 return NS_OK; |
|
3437 } |
|
3438 |
|
3439 // We now know that at least 1 existing range overlaps with the range that |
|
3440 // we are trying to add. In fact, the only ranges of interest are those at |
|
3441 // the two end points, startIndex and endIndex - 1 (which may point to the |
|
3442 // same range) as these may partially overlap the new range. Any ranges |
|
3443 // between these indices are fully overlapped by the new range, and so can be |
|
3444 // removed |
|
3445 nsTArray<RangeData> overlaps; |
|
3446 if (!overlaps.InsertElementAt(0, mRanges[startIndex])) |
|
3447 return NS_ERROR_OUT_OF_MEMORY; |
|
3448 |
|
3449 if (endIndex - 1 != startIndex) { |
|
3450 if (!overlaps.InsertElementAt(1, mRanges[endIndex - 1])) |
|
3451 return NS_ERROR_OUT_OF_MEMORY; |
|
3452 } |
|
3453 |
|
3454 // Remove all the overlapping ranges |
|
3455 for (int32_t i = startIndex; i < endIndex; ++i) { |
|
3456 mRanges[i].mRange->SetInSelection(false); |
|
3457 } |
|
3458 mRanges.RemoveElementsAt(startIndex, endIndex - startIndex); |
|
3459 |
|
3460 nsTArray<RangeData> temp; |
|
3461 for (int32_t i = overlaps.Length() - 1; i >= 0; i--) { |
|
3462 nsresult rv = SubtractRange(&overlaps[i], aItem, &temp); |
|
3463 NS_ENSURE_SUCCESS(rv, rv); |
|
3464 } |
|
3465 |
|
3466 // Insert the new element into our "leftovers" array |
|
3467 int32_t insertionPoint; |
|
3468 rv = FindInsertionPoint(&temp, aItem->GetStartParent(), |
|
3469 aItem->StartOffset(), CompareToRangeStart, |
|
3470 &insertionPoint); |
|
3471 NS_ENSURE_SUCCESS(rv, rv); |
|
3472 |
|
3473 if (!temp.InsertElementAt(insertionPoint, RangeData(aItem))) |
|
3474 return NS_ERROR_OUT_OF_MEMORY; |
|
3475 |
|
3476 // Merge the leftovers back in to mRanges |
|
3477 if (!mRanges.InsertElementsAt(startIndex, temp)) |
|
3478 return NS_ERROR_OUT_OF_MEMORY; |
|
3479 |
|
3480 for (uint32_t i = 0; i < temp.Length(); ++i) { |
|
3481 temp[i].mRange->SetInSelection(true); |
|
3482 } |
|
3483 |
|
3484 *aOutIndex = startIndex + insertionPoint; |
|
3485 return NS_OK; |
|
3486 } |
|
3487 |
|
3488 nsresult |
|
3489 Selection::RemoveItem(nsRange* aItem) |
|
3490 { |
|
3491 if (!aItem) |
|
3492 return NS_ERROR_NULL_POINTER; |
|
3493 |
|
3494 // Find the range's index & remove it. We could use FindInsertionPoint to |
|
3495 // get O(log n) time, but that requires many expensive DOM comparisons. |
|
3496 // For even several thousand items, this is probably faster because the |
|
3497 // comparisons are so fast. |
|
3498 int32_t idx = -1; |
|
3499 uint32_t i; |
|
3500 for (i = 0; i < mRanges.Length(); i ++) { |
|
3501 if (mRanges[i].mRange == aItem) { |
|
3502 idx = (int32_t)i; |
|
3503 break; |
|
3504 } |
|
3505 } |
|
3506 if (idx < 0) |
|
3507 return NS_ERROR_INVALID_ARG; |
|
3508 |
|
3509 mRanges.RemoveElementAt(idx); |
|
3510 aItem->SetInSelection(false); |
|
3511 return NS_OK; |
|
3512 } |
|
3513 |
|
3514 nsresult |
|
3515 Selection::RemoveCollapsedRanges() |
|
3516 { |
|
3517 uint32_t i = 0; |
|
3518 while (i < mRanges.Length()) { |
|
3519 if (mRanges[i].mRange->Collapsed()) { |
|
3520 nsresult rv = RemoveItem(mRanges[i].mRange); |
|
3521 NS_ENSURE_SUCCESS(rv, rv); |
|
3522 } else { |
|
3523 ++i; |
|
3524 } |
|
3525 } |
|
3526 return NS_OK; |
|
3527 } |
|
3528 |
|
3529 nsresult |
|
3530 Selection::Clear(nsPresContext* aPresContext) |
|
3531 { |
|
3532 setAnchorFocusRange(-1); |
|
3533 |
|
3534 for (uint32_t i = 0; i < mRanges.Length(); ++i) { |
|
3535 mRanges[i].mRange->SetInSelection(false); |
|
3536 selectFrames(aPresContext, mRanges[i].mRange, false); |
|
3537 } |
|
3538 mRanges.Clear(); |
|
3539 |
|
3540 // Reset direction so for more dependable table selection range handling |
|
3541 SetDirection(eDirNext); |
|
3542 |
|
3543 // If this was an ATTENTION selection, change it back to normal now |
|
3544 if (mFrameSelection && |
|
3545 mFrameSelection->GetDisplaySelection() == |
|
3546 nsISelectionController::SELECTION_ATTENTION) { |
|
3547 mFrameSelection->SetDisplaySelection(nsISelectionController::SELECTION_ON); |
|
3548 } |
|
3549 |
|
3550 return NS_OK; |
|
3551 } |
|
3552 |
|
3553 NS_IMETHODIMP |
|
3554 Selection::GetType(int16_t* aType) |
|
3555 { |
|
3556 NS_ENSURE_ARG_POINTER(aType); |
|
3557 *aType = Type(); |
|
3558 |
|
3559 return NS_OK; |
|
3560 } |
|
3561 |
|
3562 // RangeMatches*Point |
|
3563 // |
|
3564 // Compares the range beginning or ending point, and returns true if it |
|
3565 // exactly matches the given DOM point. |
|
3566 |
|
3567 static inline bool |
|
3568 RangeMatchesBeginPoint(nsRange* aRange, nsINode* aNode, int32_t aOffset) |
|
3569 { |
|
3570 return aRange->GetStartParent() == aNode && aRange->StartOffset() == aOffset; |
|
3571 } |
|
3572 |
|
3573 static inline bool |
|
3574 RangeMatchesEndPoint(nsRange* aRange, nsINode* aNode, int32_t aOffset) |
|
3575 { |
|
3576 return aRange->GetEndParent() == aNode && aRange->EndOffset() == aOffset; |
|
3577 } |
|
3578 |
|
3579 // Selection::EqualsRangeAtPoint |
|
3580 // |
|
3581 // Utility method for checking equivalence of two ranges. |
|
3582 |
|
3583 bool |
|
3584 Selection::EqualsRangeAtPoint( |
|
3585 nsINode* aBeginNode, int32_t aBeginOffset, |
|
3586 nsINode* aEndNode, int32_t aEndOffset, |
|
3587 int32_t aRangeIndex) |
|
3588 { |
|
3589 if (aRangeIndex >=0 && aRangeIndex < (int32_t) mRanges.Length()) { |
|
3590 nsRange* range = mRanges[aRangeIndex].mRange; |
|
3591 if (RangeMatchesBeginPoint(range, aBeginNode, aBeginOffset) && |
|
3592 RangeMatchesEndPoint(range, aEndNode, aEndOffset)) |
|
3593 return true; |
|
3594 } |
|
3595 return false; |
|
3596 } |
|
3597 |
|
3598 // Selection::GetRangesForInterval |
|
3599 // |
|
3600 // XPCOM wrapper for the nsTArray version |
|
3601 |
|
3602 NS_IMETHODIMP |
|
3603 Selection::GetRangesForInterval(nsIDOMNode* aBeginNode, int32_t aBeginOffset, |
|
3604 nsIDOMNode* aEndNode, int32_t aEndOffset, |
|
3605 bool aAllowAdjacent, |
|
3606 uint32_t* aResultCount, |
|
3607 nsIDOMRange*** aResults) |
|
3608 { |
|
3609 if (!aBeginNode || ! aEndNode || ! aResultCount || ! aResults) |
|
3610 return NS_ERROR_NULL_POINTER; |
|
3611 |
|
3612 *aResultCount = 0; |
|
3613 *aResults = nullptr; |
|
3614 |
|
3615 nsTArray<nsRefPtr<nsRange>> results; |
|
3616 ErrorResult result; |
|
3617 nsCOMPtr<nsINode> beginNode = do_QueryInterface(aBeginNode); |
|
3618 nsCOMPtr<nsINode> endNode = do_QueryInterface(aEndNode); |
|
3619 NS_ENSURE_TRUE(beginNode && endNode, NS_ERROR_NULL_POINTER); |
|
3620 GetRangesForInterval(*beginNode, aBeginOffset, *endNode, aEndOffset, |
|
3621 aAllowAdjacent, results, result); |
|
3622 if (result.Failed()) { |
|
3623 return result.ErrorCode(); |
|
3624 } |
|
3625 *aResultCount = results.Length(); |
|
3626 if (*aResultCount == 0) { |
|
3627 return NS_OK; |
|
3628 } |
|
3629 |
|
3630 *aResults = static_cast<nsIDOMRange**> |
|
3631 (nsMemory::Alloc(sizeof(nsIDOMRange*) * *aResultCount)); |
|
3632 NS_ENSURE_TRUE(*aResults, NS_ERROR_OUT_OF_MEMORY); |
|
3633 |
|
3634 for (uint32_t i = 0; i < *aResultCount; i++) { |
|
3635 (*aResults)[i] = results[i].forget().take(); |
|
3636 } |
|
3637 return NS_OK; |
|
3638 } |
|
3639 |
|
3640 |
|
3641 void |
|
3642 Selection::GetRangesForInterval(nsINode& aBeginNode, int32_t aBeginOffset, |
|
3643 nsINode& aEndNode, int32_t aEndOffset, |
|
3644 bool aAllowAdjacent, |
|
3645 nsTArray<nsRefPtr<nsRange>>& aReturn, |
|
3646 mozilla::ErrorResult& aRv) |
|
3647 { |
|
3648 nsTArray<nsRange*> results; |
|
3649 nsresult rv = GetRangesForIntervalArray(&aBeginNode, aBeginOffset, |
|
3650 &aEndNode, aEndOffset, |
|
3651 aAllowAdjacent, &results); |
|
3652 if (NS_FAILED(rv)) { |
|
3653 aRv.Throw(rv); |
|
3654 return; |
|
3655 } |
|
3656 |
|
3657 aReturn.SetLength(results.Length()); |
|
3658 for (uint32_t i = 0; i < results.Length(); ++i) { |
|
3659 aReturn[i] = results[i]; // AddRefs |
|
3660 } |
|
3661 } |
|
3662 |
|
3663 // Selection::GetRangesForIntervalArray |
|
3664 // |
|
3665 // Fills a nsTArray with the ranges overlapping the range specified by |
|
3666 // the given endpoints. Ranges in the selection exactly adjacent to the |
|
3667 // input range are not returned unless aAllowAdjacent is set. |
|
3668 // |
|
3669 // For example, if the following ranges were in the selection |
|
3670 // (assume everything is within the same node) |
|
3671 // |
|
3672 // Start Offset: 0 2 7 9 |
|
3673 // End Offset: 2 5 9 10 |
|
3674 // |
|
3675 // and passed aBeginOffset of 2 and aEndOffset of 9, then with |
|
3676 // aAllowAdjacent set, all the ranges should be returned. If |
|
3677 // aAllowAdjacent was false, the ranges [2, 5] and [7, 9] only |
|
3678 // should be returned |
|
3679 // |
|
3680 // Now that overlapping ranges are disallowed, there can be a maximum of |
|
3681 // 2 adjacent ranges |
|
3682 |
|
3683 nsresult |
|
3684 Selection::GetRangesForIntervalArray(nsINode* aBeginNode, int32_t aBeginOffset, |
|
3685 nsINode* aEndNode, int32_t aEndOffset, |
|
3686 bool aAllowAdjacent, |
|
3687 nsTArray<nsRange*>* aRanges) |
|
3688 { |
|
3689 aRanges->Clear(); |
|
3690 int32_t startIndex, endIndex; |
|
3691 nsresult res = GetIndicesForInterval(aBeginNode, aBeginOffset, |
|
3692 aEndNode, aEndOffset, aAllowAdjacent, |
|
3693 &startIndex, &endIndex); |
|
3694 NS_ENSURE_SUCCESS(res, res); |
|
3695 |
|
3696 if (startIndex == -1 || endIndex == -1) |
|
3697 return NS_OK; |
|
3698 |
|
3699 for (int32_t i = startIndex; i < endIndex; i++) { |
|
3700 if (!aRanges->AppendElement(mRanges[i].mRange)) |
|
3701 return NS_ERROR_OUT_OF_MEMORY; |
|
3702 } |
|
3703 |
|
3704 return NS_OK; |
|
3705 } |
|
3706 |
|
3707 // Selection::GetIndicesForInterval |
|
3708 // |
|
3709 // Works on the same principle as GetRangesForIntervalArray above, however |
|
3710 // instead this returns the indices into mRanges between which the |
|
3711 // overlapping ranges lie. |
|
3712 |
|
3713 nsresult |
|
3714 Selection::GetIndicesForInterval(nsINode* aBeginNode, int32_t aBeginOffset, |
|
3715 nsINode* aEndNode, int32_t aEndOffset, |
|
3716 bool aAllowAdjacent, |
|
3717 int32_t* aStartIndex, int32_t* aEndIndex) |
|
3718 { |
|
3719 int32_t startIndex; |
|
3720 int32_t endIndex; |
|
3721 |
|
3722 if (!aStartIndex) |
|
3723 aStartIndex = &startIndex; |
|
3724 if (!aEndIndex) |
|
3725 aEndIndex = &endIndex; |
|
3726 |
|
3727 *aStartIndex = -1; |
|
3728 *aEndIndex = -1; |
|
3729 |
|
3730 if (mRanges.Length() == 0) |
|
3731 return NS_OK; |
|
3732 |
|
3733 bool intervalIsCollapsed = aBeginNode == aEndNode && |
|
3734 aBeginOffset == aEndOffset; |
|
3735 |
|
3736 // Ranges that end before the given interval and begin after the given |
|
3737 // interval can be discarded |
|
3738 int32_t endsBeforeIndex; |
|
3739 if (NS_FAILED(FindInsertionPoint(&mRanges, aEndNode, aEndOffset, |
|
3740 &CompareToRangeStart, |
|
3741 &endsBeforeIndex))) { |
|
3742 return NS_OK; |
|
3743 } |
|
3744 |
|
3745 if (endsBeforeIndex == 0) { |
|
3746 nsRange* endRange = mRanges[endsBeforeIndex].mRange; |
|
3747 |
|
3748 // If the interval is strictly before the range at index 0, we can optimize |
|
3749 // by returning now - all ranges start after the given interval |
|
3750 if (!RangeMatchesBeginPoint(endRange, aEndNode, aEndOffset)) |
|
3751 return NS_OK; |
|
3752 |
|
3753 // We now know that the start point of mRanges[0].mRange equals the end of |
|
3754 // the interval. Thus, when aAllowadjacent is true, the caller is always |
|
3755 // interested in this range. However, when excluding adjacencies, we must |
|
3756 // remember to include the range when both it and the given interval are |
|
3757 // collapsed to the same point |
|
3758 if (!aAllowAdjacent && !(endRange->Collapsed() && intervalIsCollapsed)) |
|
3759 return NS_OK; |
|
3760 } |
|
3761 *aEndIndex = endsBeforeIndex; |
|
3762 |
|
3763 int32_t beginsAfterIndex; |
|
3764 if (NS_FAILED(FindInsertionPoint(&mRanges, aBeginNode, aBeginOffset, |
|
3765 &CompareToRangeEnd, |
|
3766 &beginsAfterIndex))) { |
|
3767 return NS_OK; |
|
3768 } |
|
3769 if (beginsAfterIndex == (int32_t) mRanges.Length()) |
|
3770 return NS_OK; // optimization: all ranges are strictly before us |
|
3771 |
|
3772 if (aAllowAdjacent) { |
|
3773 // At this point, one of the following holds: |
|
3774 // endsBeforeIndex == mRanges.Length(), |
|
3775 // endsBeforeIndex points to a range whose start point does not equal the |
|
3776 // given interval's start point |
|
3777 // endsBeforeIndex points to a range whose start point equals the given |
|
3778 // interval's start point |
|
3779 // In the final case, there can be two such ranges, a collapsed range, and |
|
3780 // an adjacent range (they will appear in mRanges in that order). For this |
|
3781 // final case, we need to increment endsBeforeIndex, until one of the |
|
3782 // first two possibilites hold |
|
3783 while (endsBeforeIndex < (int32_t) mRanges.Length()) { |
|
3784 nsRange* endRange = mRanges[endsBeforeIndex].mRange; |
|
3785 if (!RangeMatchesBeginPoint(endRange, aEndNode, aEndOffset)) |
|
3786 break; |
|
3787 endsBeforeIndex++; |
|
3788 } |
|
3789 |
|
3790 // Likewise, one of the following holds: |
|
3791 // beginsAfterIndex == 0, |
|
3792 // beginsAfterIndex points to a range whose end point does not equal |
|
3793 // the given interval's end point |
|
3794 // beginsOnOrAfter points to a range whose end point equals the given |
|
3795 // interval's end point |
|
3796 // In the final case, there can be two such ranges, an adjacent range, and |
|
3797 // a collapsed range (they will appear in mRanges in that order). For this |
|
3798 // final case, we only need to take action if both those ranges exist, and |
|
3799 // we are pointing to the collapsed range - we need to point to the |
|
3800 // adjacent range |
|
3801 nsRange* beginRange = mRanges[beginsAfterIndex].mRange; |
|
3802 if (beginsAfterIndex > 0 && beginRange->Collapsed() && |
|
3803 RangeMatchesEndPoint(beginRange, aBeginNode, aBeginOffset)) { |
|
3804 beginRange = mRanges[beginsAfterIndex - 1].mRange; |
|
3805 if (RangeMatchesEndPoint(beginRange, aBeginNode, aBeginOffset)) |
|
3806 beginsAfterIndex--; |
|
3807 } |
|
3808 } else { |
|
3809 // See above for the possibilities at this point. The only case where we |
|
3810 // need to take action is when the range at beginsAfterIndex ends on |
|
3811 // the given interval's start point, but that range isn't collapsed (a |
|
3812 // collapsed range should be included in the returned results). |
|
3813 nsRange* beginRange = mRanges[beginsAfterIndex].mRange; |
|
3814 if (RangeMatchesEndPoint(beginRange, aBeginNode, aBeginOffset) && |
|
3815 !beginRange->Collapsed()) |
|
3816 beginsAfterIndex++; |
|
3817 |
|
3818 // Again, see above for the meaning of endsBeforeIndex at this point. |
|
3819 // In particular, endsBeforeIndex may point to a collaped range which |
|
3820 // represents the point at the end of the interval - this range should be |
|
3821 // included |
|
3822 if (endsBeforeIndex < (int32_t) mRanges.Length()) { |
|
3823 nsRange* endRange = mRanges[endsBeforeIndex].mRange; |
|
3824 if (RangeMatchesBeginPoint(endRange, aEndNode, aEndOffset) && |
|
3825 endRange->Collapsed()) |
|
3826 endsBeforeIndex++; |
|
3827 } |
|
3828 } |
|
3829 |
|
3830 NS_ASSERTION(beginsAfterIndex <= endsBeforeIndex, |
|
3831 "Is mRanges not ordered?"); |
|
3832 NS_ENSURE_STATE(beginsAfterIndex <= endsBeforeIndex); |
|
3833 |
|
3834 *aStartIndex = beginsAfterIndex; |
|
3835 *aEndIndex = endsBeforeIndex; |
|
3836 return NS_OK; |
|
3837 } |
|
3838 |
|
3839 NS_IMETHODIMP |
|
3840 Selection::GetPrimaryFrameForAnchorNode(nsIFrame** aReturnFrame) |
|
3841 { |
|
3842 if (!aReturnFrame) |
|
3843 return NS_ERROR_NULL_POINTER; |
|
3844 |
|
3845 int32_t frameOffset = 0; |
|
3846 *aReturnFrame = 0; |
|
3847 nsCOMPtr<nsIContent> content = do_QueryInterface(GetAnchorNode()); |
|
3848 if (content && mFrameSelection) |
|
3849 { |
|
3850 *aReturnFrame = mFrameSelection-> |
|
3851 GetFrameForNodeOffset(content, AnchorOffset(), |
|
3852 mFrameSelection->GetHint(), &frameOffset); |
|
3853 if (*aReturnFrame) |
|
3854 return NS_OK; |
|
3855 } |
|
3856 return NS_ERROR_FAILURE; |
|
3857 } |
|
3858 |
|
3859 NS_IMETHODIMP |
|
3860 Selection::GetPrimaryFrameForFocusNode(nsIFrame** aReturnFrame, |
|
3861 int32_t* aOffsetUsed, |
|
3862 bool aVisual) |
|
3863 { |
|
3864 if (!aReturnFrame) |
|
3865 return NS_ERROR_NULL_POINTER; |
|
3866 |
|
3867 nsCOMPtr<nsIContent> content = do_QueryInterface(GetFocusNode()); |
|
3868 if (!content || !mFrameSelection) |
|
3869 return NS_ERROR_FAILURE; |
|
3870 |
|
3871 int32_t frameOffset = 0; |
|
3872 *aReturnFrame = 0; |
|
3873 if (!aOffsetUsed) |
|
3874 aOffsetUsed = &frameOffset; |
|
3875 |
|
3876 nsFrameSelection::HINT hint = mFrameSelection->GetHint(); |
|
3877 |
|
3878 if (aVisual) { |
|
3879 nsIPresShell *presShell = mFrameSelection->GetShell(); |
|
3880 if (!presShell) |
|
3881 return NS_ERROR_FAILURE; |
|
3882 |
|
3883 nsRefPtr<nsCaret> caret = presShell->GetCaret(); |
|
3884 if (!caret) |
|
3885 return NS_ERROR_FAILURE; |
|
3886 |
|
3887 uint8_t caretBidiLevel = mFrameSelection->GetCaretBidiLevel(); |
|
3888 |
|
3889 return caret->GetCaretFrameForNodeOffset(content, FocusOffset(), |
|
3890 hint, caretBidiLevel, aReturnFrame, aOffsetUsed); |
|
3891 } |
|
3892 |
|
3893 *aReturnFrame = mFrameSelection-> |
|
3894 GetFrameForNodeOffset(content, FocusOffset(), |
|
3895 hint, aOffsetUsed); |
|
3896 if (!*aReturnFrame) |
|
3897 return NS_ERROR_FAILURE; |
|
3898 |
|
3899 return NS_OK; |
|
3900 } |
|
3901 |
|
3902 //select all content children of aContent |
|
3903 nsresult |
|
3904 Selection::SelectAllFramesForContent(nsIContentIterator* aInnerIter, |
|
3905 nsIContent* aContent, |
|
3906 bool aSelected) |
|
3907 { |
|
3908 nsresult result = aInnerIter->Init(aContent); |
|
3909 nsIFrame *frame; |
|
3910 if (NS_SUCCEEDED(result)) |
|
3911 { |
|
3912 // First select frame of content passed in |
|
3913 frame = aContent->GetPrimaryFrame(); |
|
3914 if (frame && frame->GetType() == nsGkAtoms::textFrame) { |
|
3915 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame); |
|
3916 textFrame->SetSelectedRange(0, aContent->GetText()->GetLength(), aSelected, mType); |
|
3917 } |
|
3918 // Now iterated through the child frames and set them |
|
3919 while (!aInnerIter->IsDone()) { |
|
3920 nsCOMPtr<nsIContent> innercontent = |
|
3921 do_QueryInterface(aInnerIter->GetCurrentNode()); |
|
3922 |
|
3923 frame = innercontent->GetPrimaryFrame(); |
|
3924 if (frame) { |
|
3925 if (frame->GetType() == nsGkAtoms::textFrame) { |
|
3926 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame); |
|
3927 textFrame->SetSelectedRange(0, innercontent->GetText()->GetLength(), aSelected, mType); |
|
3928 } else { |
|
3929 frame->InvalidateFrameSubtree(); // frame continuations? |
|
3930 } |
|
3931 } |
|
3932 |
|
3933 aInnerIter->Next(); |
|
3934 } |
|
3935 |
|
3936 return NS_OK; |
|
3937 } |
|
3938 |
|
3939 return NS_ERROR_FAILURE; |
|
3940 } |
|
3941 |
|
3942 /** |
|
3943 * The idea of this helper method is to select or deselect "top to bottom", |
|
3944 * traversing through the frames |
|
3945 */ |
|
3946 nsresult |
|
3947 Selection::selectFrames(nsPresContext* aPresContext, nsRange* aRange, |
|
3948 bool aSelect) |
|
3949 { |
|
3950 if (!mFrameSelection || !aPresContext || !aPresContext->GetPresShell()) { |
|
3951 // nothing to do |
|
3952 return NS_OK; |
|
3953 } |
|
3954 MOZ_ASSERT(aRange); |
|
3955 |
|
3956 if (mFrameSelection->GetTableCellSelection()) { |
|
3957 nsINode* node = aRange->GetCommonAncestor(); |
|
3958 nsIFrame* frame = node->IsContent() ? node->AsContent()->GetPrimaryFrame() |
|
3959 : aPresContext->FrameManager()->GetRootFrame(); |
|
3960 if (frame) { |
|
3961 frame->InvalidateFrameSubtree(); |
|
3962 } |
|
3963 return NS_OK; |
|
3964 } |
|
3965 |
|
3966 nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator(); |
|
3967 iter->Init(aRange); |
|
3968 |
|
3969 // Loop through the content iterator for each content node; for each text |
|
3970 // node, call SetSelected on it: |
|
3971 nsCOMPtr<nsIContent> content = do_QueryInterface(aRange->GetStartParent()); |
|
3972 NS_ENSURE_STATE(content); |
|
3973 |
|
3974 // We must call first one explicitly |
|
3975 if (content->IsNodeOfType(nsINode::eTEXT)) { |
|
3976 nsIFrame* frame = content->GetPrimaryFrame(); |
|
3977 // The frame could be an SVG text frame, in which case we'll ignore it. |
|
3978 if (frame && frame->GetType() == nsGkAtoms::textFrame) { |
|
3979 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame); |
|
3980 uint32_t startOffset = aRange->StartOffset(); |
|
3981 uint32_t endOffset; |
|
3982 if (aRange->GetEndParent() == content) { |
|
3983 endOffset = aRange->EndOffset(); |
|
3984 } else { |
|
3985 endOffset = content->Length(); |
|
3986 } |
|
3987 textFrame->SetSelectedRange(startOffset, endOffset, aSelect, mType); |
|
3988 } |
|
3989 } |
|
3990 |
|
3991 iter->First(); |
|
3992 nsCOMPtr<nsIContentIterator> inneriter = NS_NewContentIterator(); |
|
3993 for (iter->First(); !iter->IsDone(); iter->Next()) { |
|
3994 content = do_QueryInterface(iter->GetCurrentNode()); |
|
3995 SelectAllFramesForContent(inneriter, content, aSelect); |
|
3996 } |
|
3997 |
|
3998 // We must now do the last one if it is not the same as the first |
|
3999 if (aRange->GetEndParent() != aRange->GetStartParent()) { |
|
4000 nsresult res; |
|
4001 content = do_QueryInterface(aRange->GetEndParent(), &res); |
|
4002 NS_ENSURE_SUCCESS(res, res); |
|
4003 NS_ENSURE_TRUE(content, res); |
|
4004 |
|
4005 if (content->IsNodeOfType(nsINode::eTEXT)) { |
|
4006 nsIFrame* frame = content->GetPrimaryFrame(); |
|
4007 // The frame could be an SVG text frame, in which case we'll ignore it. |
|
4008 if (frame && frame->GetType() == nsGkAtoms::textFrame) { |
|
4009 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame); |
|
4010 textFrame->SetSelectedRange(0, aRange->EndOffset(), aSelect, mType); |
|
4011 } |
|
4012 } |
|
4013 } |
|
4014 return NS_OK; |
|
4015 } |
|
4016 |
|
4017 |
|
4018 // Selection::LookUpSelection |
|
4019 // |
|
4020 // This function is called when a node wants to know where the selection is |
|
4021 // over itself. |
|
4022 // |
|
4023 // Usually, this is called when we already know there is a selection over |
|
4024 // the node in question, and we only need to find the boundaries of it on |
|
4025 // that node. This is when slowCheck is false--a strict test is not needed. |
|
4026 // Other times, the caller has no idea, and wants us to test everything, |
|
4027 // so we are supposed to determine whether there is a selection over the |
|
4028 // node at all. |
|
4029 // |
|
4030 // A previous version of this code used this flag to do less work when |
|
4031 // inclusion was already known (slowCheck=false). However, our tree |
|
4032 // structure allows us to quickly determine ranges overlapping the node, |
|
4033 // so we just ignore the slowCheck flag and do the full test every time. |
|
4034 // |
|
4035 // PERFORMANCE: a common case is that we are doing a fast check with exactly |
|
4036 // one range in the selection. In this case, this function is slower than |
|
4037 // brute force because of the overhead of checking the tree. We can optimize |
|
4038 // this case to make it faster by doing the same thing the previous version |
|
4039 // of this function did in the case of 1 range. This would also mean that |
|
4040 // the aSlowCheck flag would have meaning again. |
|
4041 |
|
4042 NS_IMETHODIMP |
|
4043 Selection::LookUpSelection(nsIContent* aContent, int32_t aContentOffset, |
|
4044 int32_t aContentLength, |
|
4045 SelectionDetails** aReturnDetails, |
|
4046 SelectionType aType, bool aSlowCheck) |
|
4047 { |
|
4048 nsresult rv; |
|
4049 if (!aContent || ! aReturnDetails) |
|
4050 return NS_ERROR_NULL_POINTER; |
|
4051 |
|
4052 // it is common to have no ranges, to optimize that |
|
4053 if (mRanges.Length() == 0) |
|
4054 return NS_OK; |
|
4055 |
|
4056 nsTArray<nsRange*> overlappingRanges; |
|
4057 rv = GetRangesForIntervalArray(aContent, aContentOffset, |
|
4058 aContent, aContentOffset + aContentLength, |
|
4059 false, |
|
4060 &overlappingRanges); |
|
4061 NS_ENSURE_SUCCESS(rv, rv); |
|
4062 if (overlappingRanges.Length() == 0) |
|
4063 return NS_OK; |
|
4064 |
|
4065 for (uint32_t i = 0; i < overlappingRanges.Length(); i++) { |
|
4066 nsRange* range = overlappingRanges[i]; |
|
4067 nsINode* startNode = range->GetStartParent(); |
|
4068 nsINode* endNode = range->GetEndParent(); |
|
4069 int32_t startOffset = range->StartOffset(); |
|
4070 int32_t endOffset = range->EndOffset(); |
|
4071 |
|
4072 int32_t start = -1, end = -1; |
|
4073 if (startNode == aContent && endNode == aContent) { |
|
4074 if (startOffset < (aContentOffset + aContentLength) && |
|
4075 endOffset > aContentOffset) { |
|
4076 // this range is totally inside the requested content range |
|
4077 start = std::max(0, startOffset - aContentOffset); |
|
4078 end = std::min(aContentLength, endOffset - aContentOffset); |
|
4079 } |
|
4080 // otherwise, range is inside the requested node, but does not intersect |
|
4081 // the requested content range, so ignore it |
|
4082 } else if (startNode == aContent) { |
|
4083 if (startOffset < (aContentOffset + aContentLength)) { |
|
4084 // the beginning of the range is inside the requested node, but the |
|
4085 // end is outside, select everything from there to the end |
|
4086 start = std::max(0, startOffset - aContentOffset); |
|
4087 end = aContentLength; |
|
4088 } |
|
4089 } else if (endNode == aContent) { |
|
4090 if (endOffset > aContentOffset) { |
|
4091 // the end of the range is inside the requested node, but the beginning |
|
4092 // is outside, select everything from the beginning to there |
|
4093 start = 0; |
|
4094 end = std::min(aContentLength, endOffset - aContentOffset); |
|
4095 } |
|
4096 } else { |
|
4097 // this range does not begin or end in the requested node, but since |
|
4098 // GetRangesForInterval returned this range, we know it overlaps. |
|
4099 // Therefore, this node is enclosed in the range, and we select all |
|
4100 // of it. |
|
4101 start = 0; |
|
4102 end = aContentLength; |
|
4103 } |
|
4104 if (start < 0) |
|
4105 continue; // the ranges do not overlap the input range |
|
4106 |
|
4107 SelectionDetails* details = new SelectionDetails; |
|
4108 |
|
4109 details->mNext = *aReturnDetails; |
|
4110 details->mStart = start; |
|
4111 details->mEnd = end; |
|
4112 details->mType = aType; |
|
4113 RangeData *rd = FindRangeData(range); |
|
4114 if (rd) { |
|
4115 details->mTextRangeStyle = rd->mTextRangeStyle; |
|
4116 } |
|
4117 *aReturnDetails = details; |
|
4118 } |
|
4119 return NS_OK; |
|
4120 } |
|
4121 |
|
4122 NS_IMETHODIMP |
|
4123 Selection::Repaint(nsPresContext* aPresContext) |
|
4124 { |
|
4125 int32_t arrCount = (int32_t)mRanges.Length(); |
|
4126 |
|
4127 if (arrCount < 1) |
|
4128 return NS_OK; |
|
4129 |
|
4130 int32_t i; |
|
4131 |
|
4132 for (i = 0; i < arrCount; i++) |
|
4133 { |
|
4134 nsresult rv = selectFrames(aPresContext, mRanges[i].mRange, true); |
|
4135 |
|
4136 if (NS_FAILED(rv)) { |
|
4137 return rv; |
|
4138 } |
|
4139 } |
|
4140 |
|
4141 return NS_OK; |
|
4142 } |
|
4143 |
|
4144 NS_IMETHODIMP |
|
4145 Selection::GetCanCacheFrameOffset(bool* aCanCacheFrameOffset) |
|
4146 { |
|
4147 NS_ENSURE_ARG_POINTER(aCanCacheFrameOffset); |
|
4148 |
|
4149 if (mCachedOffsetForFrame) |
|
4150 *aCanCacheFrameOffset = mCachedOffsetForFrame->mCanCacheFrameOffset; |
|
4151 else |
|
4152 *aCanCacheFrameOffset = false; |
|
4153 |
|
4154 return NS_OK; |
|
4155 } |
|
4156 |
|
4157 NS_IMETHODIMP |
|
4158 Selection::SetCanCacheFrameOffset(bool aCanCacheFrameOffset) |
|
4159 { |
|
4160 if (!mCachedOffsetForFrame) { |
|
4161 mCachedOffsetForFrame = new CachedOffsetForFrame; |
|
4162 } |
|
4163 |
|
4164 mCachedOffsetForFrame->mCanCacheFrameOffset = aCanCacheFrameOffset; |
|
4165 |
|
4166 // clean up cached frame when turn off cache |
|
4167 // fix bug 207936 |
|
4168 if (!aCanCacheFrameOffset) { |
|
4169 mCachedOffsetForFrame->mLastCaretFrame = nullptr; |
|
4170 } |
|
4171 |
|
4172 return NS_OK; |
|
4173 } |
|
4174 |
|
4175 NS_IMETHODIMP |
|
4176 Selection::GetCachedFrameOffset(nsIFrame* aFrame, int32_t inOffset, |
|
4177 nsPoint& aPoint) |
|
4178 { |
|
4179 if (!mCachedOffsetForFrame) { |
|
4180 mCachedOffsetForFrame = new CachedOffsetForFrame; |
|
4181 } |
|
4182 |
|
4183 nsresult rv = NS_OK; |
|
4184 if (mCachedOffsetForFrame->mCanCacheFrameOffset && |
|
4185 mCachedOffsetForFrame->mLastCaretFrame && |
|
4186 (aFrame == mCachedOffsetForFrame->mLastCaretFrame) && |
|
4187 (inOffset == mCachedOffsetForFrame->mLastContentOffset)) |
|
4188 { |
|
4189 // get cached frame offset |
|
4190 aPoint = mCachedOffsetForFrame->mCachedFrameOffset; |
|
4191 } |
|
4192 else |
|
4193 { |
|
4194 // Recalculate frame offset and cache it. Don't cache a frame offset if |
|
4195 // GetPointFromOffset fails, though. |
|
4196 rv = aFrame->GetPointFromOffset(inOffset, &aPoint); |
|
4197 if (NS_SUCCEEDED(rv) && mCachedOffsetForFrame->mCanCacheFrameOffset) { |
|
4198 mCachedOffsetForFrame->mCachedFrameOffset = aPoint; |
|
4199 mCachedOffsetForFrame->mLastCaretFrame = aFrame; |
|
4200 mCachedOffsetForFrame->mLastContentOffset = inOffset; |
|
4201 } |
|
4202 } |
|
4203 |
|
4204 return rv; |
|
4205 } |
|
4206 |
|
4207 NS_IMETHODIMP |
|
4208 Selection::SetAncestorLimiter(nsIContent* aContent) |
|
4209 { |
|
4210 if (mFrameSelection) |
|
4211 mFrameSelection->SetAncestorLimiter(aContent); |
|
4212 return NS_OK; |
|
4213 } |
|
4214 |
|
4215 RangeData* |
|
4216 Selection::FindRangeData(nsIDOMRange* aRange) |
|
4217 { |
|
4218 NS_ENSURE_TRUE(aRange, nullptr); |
|
4219 for (uint32_t i = 0; i < mRanges.Length(); i++) { |
|
4220 if (mRanges[i].mRange == aRange) |
|
4221 return &mRanges[i]; |
|
4222 } |
|
4223 return nullptr; |
|
4224 } |
|
4225 |
|
4226 NS_IMETHODIMP |
|
4227 Selection::SetTextRangeStyle(nsIDOMRange* aRange, |
|
4228 const TextRangeStyle& aTextRangeStyle) |
|
4229 { |
|
4230 NS_ENSURE_ARG_POINTER(aRange); |
|
4231 RangeData *rd = FindRangeData(aRange); |
|
4232 if (rd) { |
|
4233 rd->mTextRangeStyle = aTextRangeStyle; |
|
4234 } |
|
4235 return NS_OK; |
|
4236 } |
|
4237 |
|
4238 nsresult |
|
4239 Selection::StartAutoScrollTimer(nsIFrame* aFrame, nsPoint& aPoint, |
|
4240 uint32_t aDelay) |
|
4241 { |
|
4242 NS_PRECONDITION(aFrame, "Need a frame"); |
|
4243 |
|
4244 nsresult result; |
|
4245 if (!mFrameSelection) |
|
4246 return NS_OK;//nothing to do |
|
4247 |
|
4248 if (!mAutoScrollTimer) |
|
4249 { |
|
4250 mAutoScrollTimer = new nsAutoScrollTimer(); |
|
4251 |
|
4252 result = mAutoScrollTimer->Init(mFrameSelection, this); |
|
4253 |
|
4254 if (NS_FAILED(result)) |
|
4255 return result; |
|
4256 } |
|
4257 |
|
4258 result = mAutoScrollTimer->SetDelay(aDelay); |
|
4259 |
|
4260 if (NS_FAILED(result)) |
|
4261 return result; |
|
4262 |
|
4263 return DoAutoScroll(aFrame, aPoint); |
|
4264 } |
|
4265 |
|
4266 nsresult |
|
4267 Selection::StopAutoScrollTimer() |
|
4268 { |
|
4269 if (mAutoScrollTimer) |
|
4270 return mAutoScrollTimer->Stop(); |
|
4271 |
|
4272 return NS_OK; |
|
4273 } |
|
4274 |
|
4275 nsresult |
|
4276 Selection::DoAutoScroll(nsIFrame* aFrame, nsPoint& aPoint) |
|
4277 { |
|
4278 NS_PRECONDITION(aFrame, "Need a frame"); |
|
4279 |
|
4280 if (mAutoScrollTimer) |
|
4281 (void)mAutoScrollTimer->Stop(); |
|
4282 |
|
4283 nsPresContext* presContext = aFrame->PresContext(); |
|
4284 nsRootPresContext* rootPC = presContext->GetRootPresContext(); |
|
4285 if (!rootPC) |
|
4286 return NS_OK; |
|
4287 nsIFrame* rootmostFrame = rootPC->PresShell()->FrameManager()->GetRootFrame(); |
|
4288 // Get the point relative to the root most frame because the scroll we are |
|
4289 // about to do will change the coordinates of aFrame. |
|
4290 nsPoint globalPoint = aPoint + aFrame->GetOffsetToCrossDoc(rootmostFrame); |
|
4291 |
|
4292 bool didScroll = presContext->PresShell()->ScrollFrameRectIntoView( |
|
4293 aFrame, |
|
4294 nsRect(aPoint, nsSize(0, 0)), |
|
4295 nsIPresShell::ScrollAxis(), |
|
4296 nsIPresShell::ScrollAxis(), |
|
4297 0); |
|
4298 |
|
4299 // |
|
4300 // Start the AutoScroll timer if necessary. |
|
4301 // |
|
4302 |
|
4303 if (didScroll && mAutoScrollTimer) |
|
4304 { |
|
4305 nsPoint presContextPoint = globalPoint - |
|
4306 presContext->PresShell()->FrameManager()->GetRootFrame()->GetOffsetToCrossDoc(rootmostFrame); |
|
4307 mAutoScrollTimer->Start(presContext, presContextPoint); |
|
4308 } |
|
4309 |
|
4310 return NS_OK; |
|
4311 } |
|
4312 |
|
4313 |
|
4314 /** RemoveAllRanges zeroes the selection |
|
4315 */ |
|
4316 NS_IMETHODIMP |
|
4317 Selection::RemoveAllRanges() |
|
4318 { |
|
4319 ErrorResult result; |
|
4320 RemoveAllRanges(result); |
|
4321 return result.ErrorCode(); |
|
4322 } |
|
4323 |
|
4324 void |
|
4325 Selection::RemoveAllRanges(ErrorResult& aRv) |
|
4326 { |
|
4327 if (!mFrameSelection) |
|
4328 return; // nothing to do |
|
4329 nsRefPtr<nsPresContext> presContext = GetPresContext(); |
|
4330 nsresult result = Clear(presContext); |
|
4331 if (NS_FAILED(result)) { |
|
4332 aRv.Throw(result); |
|
4333 return; |
|
4334 } |
|
4335 |
|
4336 // Turn off signal for table selection |
|
4337 mFrameSelection->ClearTableCellSelection(); |
|
4338 |
|
4339 result = mFrameSelection->NotifySelectionListeners(GetType()); |
|
4340 // Also need to notify the frames! |
|
4341 // PresShell::CharacterDataChanged should do that on DocumentChanged |
|
4342 if (NS_FAILED(result)) { |
|
4343 aRv.Throw(result); |
|
4344 } |
|
4345 } |
|
4346 |
|
4347 /** AddRange adds the specified range to the selection |
|
4348 * @param aRange is the range to be added |
|
4349 */ |
|
4350 NS_IMETHODIMP |
|
4351 Selection::AddRange(nsIDOMRange* aDOMRange) |
|
4352 { |
|
4353 if (!aDOMRange) { |
|
4354 return NS_ERROR_NULL_POINTER; |
|
4355 } |
|
4356 nsRange* range = static_cast<nsRange*>(aDOMRange); |
|
4357 ErrorResult result; |
|
4358 AddRange(*range, result); |
|
4359 return result.ErrorCode(); |
|
4360 } |
|
4361 |
|
4362 void |
|
4363 Selection::AddRange(nsRange& aRange, ErrorResult& aRv) |
|
4364 { |
|
4365 // This inserts a table cell range in proper document order |
|
4366 // and returns NS_OK if range doesn't contain just one table cell |
|
4367 bool didAddRange; |
|
4368 int32_t rangeIndex; |
|
4369 nsresult result = addTableCellRange(&aRange, &didAddRange, &rangeIndex); |
|
4370 if (NS_FAILED(result)) { |
|
4371 aRv.Throw(result); |
|
4372 return; |
|
4373 } |
|
4374 |
|
4375 if (!didAddRange) |
|
4376 { |
|
4377 result = AddItem(&aRange, &rangeIndex); |
|
4378 if (NS_FAILED(result)) { |
|
4379 aRv.Throw(result); |
|
4380 return; |
|
4381 } |
|
4382 } |
|
4383 |
|
4384 NS_ASSERTION(rangeIndex >= 0, "Range index not returned"); |
|
4385 setAnchorFocusRange(rangeIndex); |
|
4386 |
|
4387 // Make sure the caret appears on the next line, if at a newline |
|
4388 if (mType == nsISelectionController::SELECTION_NORMAL) |
|
4389 SetInterlinePosition(true); |
|
4390 |
|
4391 nsRefPtr<nsPresContext> presContext = GetPresContext(); |
|
4392 selectFrames(presContext, &aRange, true); |
|
4393 |
|
4394 if (!mFrameSelection) |
|
4395 return;//nothing to do |
|
4396 |
|
4397 result = mFrameSelection->NotifySelectionListeners(GetType()); |
|
4398 if (NS_FAILED(result)) { |
|
4399 aRv.Throw(result); |
|
4400 } |
|
4401 } |
|
4402 |
|
4403 // Selection::RemoveRange |
|
4404 // |
|
4405 // Removes the given range from the selection. The tricky part is updating |
|
4406 // the flags on the frames that indicate whether they have a selection or |
|
4407 // not. There could be several selection ranges on the frame, and clearing |
|
4408 // the bit would cause the selection to not be drawn, even when there is |
|
4409 // another range on the frame (bug 346185). |
|
4410 // |
|
4411 // We therefore find any ranges that intersect the same nodes as the range |
|
4412 // being removed, and cause them to set the selected bits back on their |
|
4413 // selected frames after we've cleared the bit from ours. |
|
4414 |
|
4415 nsresult |
|
4416 Selection::RemoveRange(nsIDOMRange* aDOMRange) |
|
4417 { |
|
4418 if (!aDOMRange) { |
|
4419 return NS_ERROR_INVALID_ARG; |
|
4420 } |
|
4421 nsRange* range = static_cast<nsRange*>(aDOMRange); |
|
4422 ErrorResult result; |
|
4423 RemoveRange(*range, result); |
|
4424 return result.ErrorCode(); |
|
4425 } |
|
4426 |
|
4427 void |
|
4428 Selection::RemoveRange(nsRange& aRange, ErrorResult& aRv) |
|
4429 { |
|
4430 nsresult rv = RemoveItem(&aRange); |
|
4431 if (NS_FAILED(rv)) { |
|
4432 aRv.Throw(rv); |
|
4433 return; |
|
4434 } |
|
4435 |
|
4436 nsINode* beginNode = aRange.GetStartParent(); |
|
4437 nsINode* endNode = aRange.GetEndParent(); |
|
4438 |
|
4439 if (!beginNode || !endNode) { |
|
4440 // Detached range; nothing else to do here. |
|
4441 return; |
|
4442 } |
|
4443 |
|
4444 // find out the length of the end node, so we can select all of it |
|
4445 int32_t beginOffset, endOffset; |
|
4446 if (endNode->IsNodeOfType(nsINode::eTEXT)) { |
|
4447 // Get the length of the text. We can't just use the offset because |
|
4448 // another range could be touching this text node but not intersect our |
|
4449 // range. |
|
4450 beginOffset = 0; |
|
4451 endOffset = static_cast<nsIContent*>(endNode)->TextLength(); |
|
4452 } else { |
|
4453 // For non-text nodes, the given offsets should be sufficient. |
|
4454 beginOffset = aRange.StartOffset(); |
|
4455 endOffset = aRange.EndOffset(); |
|
4456 } |
|
4457 |
|
4458 // clear the selected bit from the removed range's frames |
|
4459 nsRefPtr<nsPresContext> presContext = GetPresContext(); |
|
4460 selectFrames(presContext, &aRange, false); |
|
4461 |
|
4462 // add back the selected bit for each range touching our nodes |
|
4463 nsTArray<nsRange*> affectedRanges; |
|
4464 rv = GetRangesForIntervalArray(beginNode, beginOffset, |
|
4465 endNode, endOffset, |
|
4466 true, &affectedRanges); |
|
4467 if (NS_FAILED(rv)) { |
|
4468 aRv.Throw(rv); |
|
4469 return; |
|
4470 } |
|
4471 for (uint32_t i = 0; i < affectedRanges.Length(); i++) { |
|
4472 selectFrames(presContext, affectedRanges[i], true); |
|
4473 } |
|
4474 |
|
4475 int32_t cnt = mRanges.Length(); |
|
4476 if (&aRange == mAnchorFocusRange) { |
|
4477 // Reset anchor to LAST range or clear it if there are no ranges. |
|
4478 setAnchorFocusRange(cnt - 1); |
|
4479 |
|
4480 // When the selection is user-created it makes sense to scroll the range |
|
4481 // into view. The spell-check selection, however, is created and destroyed |
|
4482 // in the background. We don't want to scroll in this case or the view |
|
4483 // might appear to be moving randomly (bug 337871). |
|
4484 if (mType != nsISelectionController::SELECTION_SPELLCHECK && cnt > 0) |
|
4485 ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION); |
|
4486 } |
|
4487 |
|
4488 if (!mFrameSelection) |
|
4489 return;//nothing to do |
|
4490 rv = mFrameSelection->NotifySelectionListeners(GetType()); |
|
4491 if (NS_FAILED(rv)) { |
|
4492 aRv.Throw(rv); |
|
4493 } |
|
4494 } |
|
4495 |
|
4496 |
|
4497 |
|
4498 /* |
|
4499 * Collapse sets the whole selection to be one point. |
|
4500 */ |
|
4501 NS_IMETHODIMP |
|
4502 Selection::Collapse(nsIDOMNode* aParentNode, int32_t aOffset) |
|
4503 { |
|
4504 nsCOMPtr<nsINode> parentNode = do_QueryInterface(aParentNode); |
|
4505 return Collapse(parentNode, aOffset); |
|
4506 } |
|
4507 |
|
4508 NS_IMETHODIMP |
|
4509 Selection::CollapseNative(nsINode* aParentNode, int32_t aOffset) |
|
4510 { |
|
4511 return Collapse(aParentNode, aOffset); |
|
4512 } |
|
4513 |
|
4514 nsresult |
|
4515 Selection::Collapse(nsINode* aParentNode, int32_t aOffset) |
|
4516 { |
|
4517 if (!aParentNode) |
|
4518 return NS_ERROR_INVALID_ARG; |
|
4519 |
|
4520 ErrorResult result; |
|
4521 Collapse(*aParentNode, static_cast<uint32_t>(aOffset), result); |
|
4522 return result.ErrorCode(); |
|
4523 } |
|
4524 |
|
4525 void |
|
4526 Selection::Collapse(nsINode& aParentNode, uint32_t aOffset, ErrorResult& aRv) |
|
4527 { |
|
4528 if (!mFrameSelection) { |
|
4529 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection |
|
4530 return; |
|
4531 } |
|
4532 |
|
4533 nsCOMPtr<nsINode> kungfuDeathGrip = &aParentNode; |
|
4534 |
|
4535 mFrameSelection->InvalidateDesiredX(); |
|
4536 if (!IsValidSelectionPoint(mFrameSelection, &aParentNode)) { |
|
4537 aRv.Throw(NS_ERROR_FAILURE); |
|
4538 return; |
|
4539 } |
|
4540 nsresult result; |
|
4541 |
|
4542 nsRefPtr<nsPresContext> presContext = GetPresContext(); |
|
4543 if (!presContext || presContext->Document() != aParentNode.OwnerDoc()) { |
|
4544 aRv.Throw(NS_ERROR_FAILURE); |
|
4545 return; |
|
4546 } |
|
4547 |
|
4548 // Delete all of the current ranges |
|
4549 Clear(presContext); |
|
4550 |
|
4551 // Turn off signal for table selection |
|
4552 mFrameSelection->ClearTableCellSelection(); |
|
4553 |
|
4554 nsRefPtr<nsRange> range = new nsRange(&aParentNode); |
|
4555 result = range->SetEnd(&aParentNode, aOffset); |
|
4556 if (NS_FAILED(result)) { |
|
4557 aRv.Throw(result); |
|
4558 return; |
|
4559 } |
|
4560 result = range->SetStart(&aParentNode, aOffset); |
|
4561 if (NS_FAILED(result)) { |
|
4562 aRv.Throw(result); |
|
4563 return; |
|
4564 } |
|
4565 |
|
4566 #ifdef DEBUG_SELECTION |
|
4567 nsCOMPtr<nsIContent> content = do_QueryInterface(&aParentNode); |
|
4568 nsCOMPtr<nsIDocument> doc = do_QueryInterface(&aParentNode); |
|
4569 printf ("Sel. Collapse to %p %s %d\n", &aParentNode, |
|
4570 content ? nsAtomCString(content->Tag()).get() |
|
4571 : (doc ? "DOCUMENT" : "???"), |
|
4572 aOffset); |
|
4573 #endif |
|
4574 |
|
4575 int32_t rangeIndex = -1; |
|
4576 result = AddItem(range, &rangeIndex); |
|
4577 if (NS_FAILED(result)) { |
|
4578 aRv.Throw(result); |
|
4579 return; |
|
4580 } |
|
4581 setAnchorFocusRange(0); |
|
4582 selectFrames(presContext, range, true); |
|
4583 result = mFrameSelection->NotifySelectionListeners(GetType()); |
|
4584 if (NS_FAILED(result)) { |
|
4585 aRv.Throw(result); |
|
4586 } |
|
4587 } |
|
4588 |
|
4589 /* |
|
4590 * Sets the whole selection to be one point |
|
4591 * at the start of the current selection |
|
4592 */ |
|
4593 NS_IMETHODIMP |
|
4594 Selection::CollapseToStart() |
|
4595 { |
|
4596 ErrorResult result; |
|
4597 CollapseToStart(result); |
|
4598 return result.ErrorCode(); |
|
4599 } |
|
4600 |
|
4601 void |
|
4602 Selection::CollapseToStart(ErrorResult& aRv) |
|
4603 { |
|
4604 int32_t cnt; |
|
4605 nsresult rv = GetRangeCount(&cnt); |
|
4606 if (NS_FAILED(rv) || cnt <= 0) { |
|
4607 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
|
4608 return; |
|
4609 } |
|
4610 |
|
4611 // Get the first range |
|
4612 nsRange* firstRange = mRanges[0].mRange; |
|
4613 if (!firstRange) { |
|
4614 aRv.Throw(NS_ERROR_FAILURE); |
|
4615 return; |
|
4616 } |
|
4617 |
|
4618 if (mFrameSelection) { |
|
4619 int16_t reason = mFrameSelection->PopReason() | nsISelectionListener::COLLAPSETOSTART_REASON; |
|
4620 mFrameSelection->PostReason(reason); |
|
4621 } |
|
4622 nsINode* parent = firstRange->GetStartParent(); |
|
4623 if (!parent) { |
|
4624 aRv.Throw(NS_ERROR_FAILURE); |
|
4625 return; |
|
4626 } |
|
4627 Collapse(*parent, firstRange->StartOffset(), aRv); |
|
4628 } |
|
4629 |
|
4630 /* |
|
4631 * Sets the whole selection to be one point |
|
4632 * at the end of the current selection |
|
4633 */ |
|
4634 NS_IMETHODIMP |
|
4635 Selection::CollapseToEnd() |
|
4636 { |
|
4637 ErrorResult result; |
|
4638 CollapseToEnd(result); |
|
4639 return result.ErrorCode(); |
|
4640 } |
|
4641 |
|
4642 void |
|
4643 Selection::CollapseToEnd(ErrorResult& aRv) |
|
4644 { |
|
4645 int32_t cnt; |
|
4646 nsresult rv = GetRangeCount(&cnt); |
|
4647 if (NS_FAILED(rv) || cnt <= 0) { |
|
4648 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
|
4649 return; |
|
4650 } |
|
4651 |
|
4652 // Get the last range |
|
4653 nsRange* lastRange = mRanges[cnt - 1].mRange; |
|
4654 if (!lastRange) { |
|
4655 aRv.Throw(NS_ERROR_FAILURE); |
|
4656 return; |
|
4657 } |
|
4658 |
|
4659 if (mFrameSelection) { |
|
4660 int16_t reason = mFrameSelection->PopReason() | nsISelectionListener::COLLAPSETOEND_REASON; |
|
4661 mFrameSelection->PostReason(reason); |
|
4662 } |
|
4663 nsINode* parent = lastRange->GetEndParent(); |
|
4664 if (!parent) { |
|
4665 aRv.Throw(NS_ERROR_FAILURE); |
|
4666 return; |
|
4667 } |
|
4668 Collapse(*parent, lastRange->EndOffset(), aRv); |
|
4669 } |
|
4670 |
|
4671 /* |
|
4672 * IsCollapsed -- is the whole selection just one point, or unset? |
|
4673 */ |
|
4674 bool |
|
4675 Selection::IsCollapsed() |
|
4676 { |
|
4677 uint32_t cnt = mRanges.Length(); |
|
4678 if (cnt == 0) { |
|
4679 return true; |
|
4680 } |
|
4681 |
|
4682 if (cnt != 1) { |
|
4683 return false; |
|
4684 } |
|
4685 |
|
4686 return mRanges[0].mRange->Collapsed(); |
|
4687 } |
|
4688 |
|
4689 /* virtual */ |
|
4690 bool |
|
4691 Selection::Collapsed() |
|
4692 { |
|
4693 return IsCollapsed(); |
|
4694 } |
|
4695 |
|
4696 NS_IMETHODIMP |
|
4697 Selection::GetIsCollapsed(bool* aIsCollapsed) |
|
4698 { |
|
4699 NS_ENSURE_TRUE(aIsCollapsed, NS_ERROR_NULL_POINTER); |
|
4700 |
|
4701 *aIsCollapsed = IsCollapsed(); |
|
4702 return NS_OK; |
|
4703 } |
|
4704 |
|
4705 NS_IMETHODIMP |
|
4706 Selection::GetRangeCount(int32_t* aRangeCount) |
|
4707 { |
|
4708 *aRangeCount = (int32_t)RangeCount(); |
|
4709 |
|
4710 return NS_OK; |
|
4711 } |
|
4712 |
|
4713 NS_IMETHODIMP |
|
4714 Selection::GetRangeAt(int32_t aIndex, nsIDOMRange** aReturn) |
|
4715 { |
|
4716 ErrorResult result; |
|
4717 *aReturn = GetRangeAt(aIndex, result); |
|
4718 NS_IF_ADDREF(*aReturn); |
|
4719 return result.ErrorCode(); |
|
4720 } |
|
4721 |
|
4722 nsRange* |
|
4723 Selection::GetRangeAt(uint32_t aIndex, ErrorResult& aRv) |
|
4724 { |
|
4725 nsRange* range = GetRangeAt(aIndex); |
|
4726 if (!range) { |
|
4727 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
|
4728 return nullptr; |
|
4729 } |
|
4730 |
|
4731 return range; |
|
4732 } |
|
4733 |
|
4734 nsRange* |
|
4735 Selection::GetRangeAt(int32_t aIndex) |
|
4736 { |
|
4737 RangeData empty(nullptr); |
|
4738 return mRanges.SafeElementAt(aIndex, empty).mRange; |
|
4739 } |
|
4740 |
|
4741 /* |
|
4742 utility function |
|
4743 */ |
|
4744 nsresult |
|
4745 Selection::SetAnchorFocusToRange(nsRange* aRange) |
|
4746 { |
|
4747 NS_ENSURE_STATE(mAnchorFocusRange); |
|
4748 |
|
4749 nsresult res = RemoveItem(mAnchorFocusRange); |
|
4750 if (NS_FAILED(res)) |
|
4751 return res; |
|
4752 |
|
4753 int32_t aOutIndex = -1; |
|
4754 res = AddItem(aRange, &aOutIndex); |
|
4755 if (NS_FAILED(res)) |
|
4756 return res; |
|
4757 setAnchorFocusRange(aOutIndex); |
|
4758 |
|
4759 return NS_OK; |
|
4760 } |
|
4761 |
|
4762 void |
|
4763 Selection::ReplaceAnchorFocusRange(nsRange* aRange) |
|
4764 { |
|
4765 NS_ENSURE_TRUE_VOID(mAnchorFocusRange); |
|
4766 nsRefPtr<nsPresContext> presContext = GetPresContext(); |
|
4767 if (presContext) { |
|
4768 selectFrames(presContext, mAnchorFocusRange, false); |
|
4769 SetAnchorFocusToRange(aRange); |
|
4770 selectFrames(presContext, mAnchorFocusRange, true); |
|
4771 } |
|
4772 } |
|
4773 |
|
4774 /* |
|
4775 Notes which might come in handy for extend: |
|
4776 |
|
4777 We can tell the direction of the selection by asking for the anchors selection |
|
4778 if the begin is less than the end then we know the selection is to the "right". |
|
4779 else it is a backwards selection. |
|
4780 a = anchor |
|
4781 1 = old cursor |
|
4782 2 = new cursor |
|
4783 |
|
4784 if (a <= 1 && 1 <=2) a,1,2 or (a1,2) |
|
4785 if (a < 2 && 1 > 2) a,2,1 |
|
4786 if (1 < a && a <2) 1,a,2 |
|
4787 if (a > 2 && 2 >1) 1,2,a |
|
4788 if (2 < a && a <1) 2,a,1 |
|
4789 if (a > 1 && 1 >2) 2,1,a |
|
4790 then execute |
|
4791 a 1 2 select from 1 to 2 |
|
4792 a 2 1 deselect from 2 to 1 |
|
4793 1 a 2 deselect from 1 to a select from a to 2 |
|
4794 1 2 a deselect from 1 to 2 |
|
4795 2 1 a = continue selection from 2 to 1 |
|
4796 */ |
|
4797 |
|
4798 |
|
4799 /* |
|
4800 * Extend extends the selection away from the anchor. |
|
4801 * We don't need to know the direction, because we always change the focus. |
|
4802 */ |
|
4803 NS_IMETHODIMP |
|
4804 Selection::Extend(nsIDOMNode* aParentNode, int32_t aOffset) |
|
4805 { |
|
4806 nsCOMPtr<nsINode> parentNode = do_QueryInterface(aParentNode); |
|
4807 return Extend(parentNode, aOffset); |
|
4808 } |
|
4809 |
|
4810 NS_IMETHODIMP |
|
4811 Selection::ExtendNative(nsINode* aParentNode, int32_t aOffset) |
|
4812 { |
|
4813 return Extend(aParentNode, aOffset); |
|
4814 } |
|
4815 |
|
4816 nsresult |
|
4817 Selection::Extend(nsINode* aParentNode, int32_t aOffset) |
|
4818 { |
|
4819 if (!aParentNode) |
|
4820 return NS_ERROR_INVALID_ARG; |
|
4821 |
|
4822 ErrorResult result; |
|
4823 Extend(*aParentNode, static_cast<uint32_t>(aOffset), result); |
|
4824 return result.ErrorCode(); |
|
4825 } |
|
4826 |
|
4827 void |
|
4828 Selection::Extend(nsINode& aParentNode, uint32_t aOffset, ErrorResult& aRv) |
|
4829 { |
|
4830 // First, find the range containing the old focus point: |
|
4831 if (!mAnchorFocusRange) { |
|
4832 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
|
4833 return; |
|
4834 } |
|
4835 |
|
4836 if (!mFrameSelection) { |
|
4837 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection |
|
4838 return; |
|
4839 } |
|
4840 |
|
4841 nsresult res; |
|
4842 if (!IsValidSelectionPoint(mFrameSelection, &aParentNode)) { |
|
4843 aRv.Throw(NS_ERROR_FAILURE); |
|
4844 return; |
|
4845 } |
|
4846 |
|
4847 nsRefPtr<nsPresContext> presContext = GetPresContext(); |
|
4848 if (!presContext || presContext->Document() != aParentNode.OwnerDoc()) { |
|
4849 aRv.Throw(NS_ERROR_FAILURE); |
|
4850 return; |
|
4851 } |
|
4852 |
|
4853 //mFrameSelection->InvalidateDesiredX(); |
|
4854 |
|
4855 nsINode* anchorNode = GetAnchorNode(); |
|
4856 nsINode* focusNode = GetFocusNode(); |
|
4857 uint32_t anchorOffset = AnchorOffset(); |
|
4858 uint32_t focusOffset = FocusOffset(); |
|
4859 |
|
4860 nsRefPtr<nsRange> range = mAnchorFocusRange->CloneRange(); |
|
4861 |
|
4862 nsINode* startNode = range->GetStartParent(); |
|
4863 nsINode* endNode = range->GetEndParent(); |
|
4864 int32_t startOffset = range->StartOffset(); |
|
4865 int32_t endOffset = range->EndOffset(); |
|
4866 |
|
4867 nsDirection dir = GetDirection(); |
|
4868 |
|
4869 //compare anchor to old cursor. |
|
4870 |
|
4871 // We pass |disconnected| to the following ComparePoints calls in order |
|
4872 // to avoid assertions. ComparePoints returns 1 in the disconnected case |
|
4873 // and we can end up in various cases below, but it is assumed that in |
|
4874 // any of the cases we end up, the nsRange implementation will collapse |
|
4875 // the range to the new point because we can not make a valid range with |
|
4876 // a disconnected point. This means that whatever range is currently |
|
4877 // selected will be cleared. |
|
4878 bool disconnected = false; |
|
4879 bool shouldClearRange = false; |
|
4880 int32_t result1 = nsContentUtils::ComparePoints(anchorNode, anchorOffset, |
|
4881 focusNode, focusOffset, |
|
4882 &disconnected); |
|
4883 //compare old cursor to new cursor |
|
4884 shouldClearRange |= disconnected; |
|
4885 int32_t result2 = nsContentUtils::ComparePoints(focusNode, focusOffset, |
|
4886 &aParentNode, aOffset, |
|
4887 &disconnected); |
|
4888 //compare anchor to new cursor |
|
4889 shouldClearRange |= disconnected; |
|
4890 int32_t result3 = nsContentUtils::ComparePoints(anchorNode, anchorOffset, |
|
4891 &aParentNode, aOffset, |
|
4892 &disconnected); |
|
4893 |
|
4894 // If the points are disconnected, the range will be collapsed below, |
|
4895 // resulting in a range that selects nothing. |
|
4896 if (shouldClearRange) { |
|
4897 // Repaint the current range with the selection removed. |
|
4898 selectFrames(presContext, range, false); |
|
4899 } |
|
4900 |
|
4901 nsRefPtr<nsRange> difRange = new nsRange(&aParentNode); |
|
4902 if ((result1 == 0 && result3 < 0) || (result1 <= 0 && result2 < 0)){//a1,2 a,1,2 |
|
4903 //select from 1 to 2 unless they are collapsed |
|
4904 range->SetEnd(aParentNode, aOffset, aRv); |
|
4905 if (aRv.Failed()) { |
|
4906 return; |
|
4907 } |
|
4908 dir = eDirNext; |
|
4909 res = difRange->SetEnd(range->GetEndParent(), range->EndOffset()); |
|
4910 nsresult tmp = difRange->SetStart(focusNode, focusOffset); |
|
4911 if (NS_FAILED(tmp)) { |
|
4912 res = tmp; |
|
4913 } |
|
4914 if (NS_FAILED(res)) { |
|
4915 aRv.Throw(res); |
|
4916 return; |
|
4917 } |
|
4918 selectFrames(presContext, difRange , true); |
|
4919 res = SetAnchorFocusToRange(range); |
|
4920 if (NS_FAILED(res)) { |
|
4921 aRv.Throw(res); |
|
4922 return; |
|
4923 } |
|
4924 } |
|
4925 else if (result1 == 0 && result3 > 0){//2, a1 |
|
4926 //select from 2 to 1a |
|
4927 dir = eDirPrevious; |
|
4928 range->SetStart(aParentNode, aOffset, aRv); |
|
4929 if (aRv.Failed()) { |
|
4930 return; |
|
4931 } |
|
4932 selectFrames(presContext, range, true); |
|
4933 res = SetAnchorFocusToRange(range); |
|
4934 if (NS_FAILED(res)) { |
|
4935 aRv.Throw(res); |
|
4936 return; |
|
4937 } |
|
4938 } |
|
4939 else if (result3 <= 0 && result2 >= 0) {//a,2,1 or a2,1 or a,21 or a21 |
|
4940 //deselect from 2 to 1 |
|
4941 res = difRange->SetEnd(focusNode, focusOffset); |
|
4942 difRange->SetStart(aParentNode, aOffset, aRv); |
|
4943 if (aRv.Failed()) { |
|
4944 return; |
|
4945 } |
|
4946 if (NS_FAILED(res)) { |
|
4947 aRv.Throw(res); |
|
4948 return; |
|
4949 } |
|
4950 |
|
4951 range->SetEnd(aParentNode, aOffset, aRv); |
|
4952 if (aRv.Failed()) { |
|
4953 return; |
|
4954 } |
|
4955 res = SetAnchorFocusToRange(range); |
|
4956 if (NS_FAILED(res)) { |
|
4957 aRv.Throw(res); |
|
4958 return; |
|
4959 } |
|
4960 selectFrames(presContext, difRange, false); // deselect now |
|
4961 difRange->SetEnd(range->GetEndParent(), range->EndOffset()); |
|
4962 selectFrames(presContext, difRange, true); // must reselect last node maybe more |
|
4963 } |
|
4964 else if (result1 >= 0 && result3 <= 0) {//1,a,2 or 1a,2 or 1,a2 or 1a2 |
|
4965 if (GetDirection() == eDirPrevious){ |
|
4966 res = range->SetStart(endNode, endOffset); |
|
4967 if (NS_FAILED(res)) { |
|
4968 aRv.Throw(res); |
|
4969 return; |
|
4970 } |
|
4971 } |
|
4972 dir = eDirNext; |
|
4973 range->SetEnd(aParentNode, aOffset, aRv); |
|
4974 if (aRv.Failed()) { |
|
4975 return; |
|
4976 } |
|
4977 if (focusNode != anchorNode || focusOffset != anchorOffset) {//if collapsed diff dont do anything |
|
4978 res = difRange->SetStart(focusNode, focusOffset); |
|
4979 nsresult tmp = difRange->SetEnd(anchorNode, anchorOffset); |
|
4980 if (NS_FAILED(tmp)) { |
|
4981 res = tmp; |
|
4982 } |
|
4983 if (NS_FAILED(res)) { |
|
4984 aRv.Throw(res); |
|
4985 return; |
|
4986 } |
|
4987 res = SetAnchorFocusToRange(range); |
|
4988 if (NS_FAILED(res)) { |
|
4989 aRv.Throw(res); |
|
4990 return; |
|
4991 } |
|
4992 //deselect from 1 to a |
|
4993 selectFrames(presContext, difRange , false); |
|
4994 } |
|
4995 else |
|
4996 { |
|
4997 res = SetAnchorFocusToRange(range); |
|
4998 if (NS_FAILED(res)) { |
|
4999 aRv.Throw(res); |
|
5000 return; |
|
5001 } |
|
5002 } |
|
5003 //select from a to 2 |
|
5004 selectFrames(presContext, range , true); |
|
5005 } |
|
5006 else if (result2 <= 0 && result3 >= 0) {//1,2,a or 12,a or 1,2a or 12a |
|
5007 //deselect from 1 to 2 |
|
5008 difRange->SetEnd(aParentNode, aOffset, aRv); |
|
5009 res = difRange->SetStart(focusNode, focusOffset); |
|
5010 if (aRv.Failed()) { |
|
5011 return; |
|
5012 } |
|
5013 if (NS_FAILED(res)) { |
|
5014 aRv.Throw(res); |
|
5015 return; |
|
5016 } |
|
5017 dir = eDirPrevious; |
|
5018 range->SetStart(aParentNode, aOffset, aRv); |
|
5019 if (aRv.Failed()) { |
|
5020 return; |
|
5021 } |
|
5022 |
|
5023 res = SetAnchorFocusToRange(range); |
|
5024 if (NS_FAILED(res)) { |
|
5025 aRv.Throw(res); |
|
5026 return; |
|
5027 } |
|
5028 selectFrames(presContext, difRange , false); |
|
5029 difRange->SetStart(range->GetStartParent(), range->StartOffset()); |
|
5030 selectFrames(presContext, difRange, true);//must reselect last node |
|
5031 } |
|
5032 else if (result3 >= 0 && result1 <= 0) {//2,a,1 or 2a,1 or 2,a1 or 2a1 |
|
5033 if (GetDirection() == eDirNext){ |
|
5034 range->SetEnd(startNode, startOffset); |
|
5035 } |
|
5036 dir = eDirPrevious; |
|
5037 range->SetStart(aParentNode, aOffset, aRv); |
|
5038 if (aRv.Failed()) { |
|
5039 return; |
|
5040 } |
|
5041 //deselect from a to 1 |
|
5042 if (focusNode != anchorNode || focusOffset!= anchorOffset) {//if collapsed diff dont do anything |
|
5043 res = difRange->SetStart(anchorNode, anchorOffset); |
|
5044 nsresult tmp = difRange->SetEnd(focusNode, focusOffset); |
|
5045 if (NS_FAILED(tmp)) { |
|
5046 res = tmp; |
|
5047 } |
|
5048 tmp = SetAnchorFocusToRange(range); |
|
5049 if (NS_FAILED(tmp)) { |
|
5050 res = tmp; |
|
5051 } |
|
5052 if (NS_FAILED(res)) { |
|
5053 aRv.Throw(res); |
|
5054 return; |
|
5055 } |
|
5056 selectFrames(presContext, difRange, false); |
|
5057 } |
|
5058 else |
|
5059 { |
|
5060 res = SetAnchorFocusToRange(range); |
|
5061 if (NS_FAILED(res)) { |
|
5062 aRv.Throw(res); |
|
5063 return; |
|
5064 } |
|
5065 } |
|
5066 //select from 2 to a |
|
5067 selectFrames(presContext, range , true); |
|
5068 } |
|
5069 else if (result2 >= 0 && result1 >= 0) {//2,1,a or 21,a or 2,1a or 21a |
|
5070 //select from 2 to 1 |
|
5071 range->SetStart(aParentNode, aOffset, aRv); |
|
5072 if (aRv.Failed()) { |
|
5073 return; |
|
5074 } |
|
5075 dir = eDirPrevious; |
|
5076 res = difRange->SetEnd(focusNode, focusOffset); |
|
5077 nsresult tmp = difRange->SetStart(range->GetStartParent(), range->StartOffset()); |
|
5078 if (NS_FAILED(tmp)) { |
|
5079 res = tmp; |
|
5080 } |
|
5081 if (NS_FAILED(res)) { |
|
5082 aRv.Throw(res); |
|
5083 return; |
|
5084 } |
|
5085 |
|
5086 selectFrames(presContext, difRange, true); |
|
5087 res = SetAnchorFocusToRange(range); |
|
5088 if (NS_FAILED(res)) { |
|
5089 aRv.Throw(res); |
|
5090 return; |
|
5091 } |
|
5092 } |
|
5093 |
|
5094 DEBUG_OUT_RANGE(range); |
|
5095 #ifdef DEBUG_SELECTION |
|
5096 if (eDirNext == mDirection) |
|
5097 printf(" direction = 1 LEFT TO RIGHT\n"); |
|
5098 else |
|
5099 printf(" direction = 0 RIGHT TO LEFT\n"); |
|
5100 #endif |
|
5101 SetDirection(dir); |
|
5102 #ifdef DEBUG_SELECTION |
|
5103 nsCOMPtr<nsIContent> content = do_QueryInterface(&aParentNode); |
|
5104 |
|
5105 printf ("Sel. Extend to %p %s %d\n", content.get(), |
|
5106 nsAtomCString(content->Tag()).get(), aOffset); |
|
5107 #endif |
|
5108 res = mFrameSelection->NotifySelectionListeners(GetType()); |
|
5109 if (NS_FAILED(res)) { |
|
5110 aRv.Throw(res); |
|
5111 } |
|
5112 } |
|
5113 |
|
5114 NS_IMETHODIMP |
|
5115 Selection::SelectAllChildren(nsIDOMNode* aParentNode) |
|
5116 { |
|
5117 ErrorResult result; |
|
5118 nsCOMPtr<nsINode> node = do_QueryInterface(aParentNode); |
|
5119 NS_ENSURE_TRUE(node, NS_ERROR_INVALID_ARG); |
|
5120 SelectAllChildren(*node, result); |
|
5121 return result.ErrorCode(); |
|
5122 } |
|
5123 |
|
5124 void |
|
5125 Selection::SelectAllChildren(nsINode& aNode, ErrorResult& aRv) |
|
5126 { |
|
5127 if (mFrameSelection) |
|
5128 { |
|
5129 mFrameSelection->PostReason(nsISelectionListener::SELECTALL_REASON); |
|
5130 } |
|
5131 Collapse(aNode, 0, aRv); |
|
5132 if (aRv.Failed()) { |
|
5133 return; |
|
5134 } |
|
5135 |
|
5136 if (mFrameSelection) |
|
5137 { |
|
5138 mFrameSelection->PostReason(nsISelectionListener::SELECTALL_REASON); |
|
5139 } |
|
5140 Extend(aNode, aNode.GetChildCount(), aRv); |
|
5141 } |
|
5142 |
|
5143 NS_IMETHODIMP |
|
5144 Selection::ContainsNode(nsIDOMNode* aNode, bool aAllowPartial, bool* aYes) |
|
5145 { |
|
5146 if (!aYes) |
|
5147 return NS_ERROR_NULL_POINTER; |
|
5148 *aYes = false; |
|
5149 |
|
5150 nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
|
5151 ErrorResult result; |
|
5152 *aYes = ContainsNode(node, aAllowPartial, result); |
|
5153 return result.ErrorCode(); |
|
5154 } |
|
5155 |
|
5156 bool |
|
5157 Selection::ContainsNode(nsINode* aNode, bool aAllowPartial, ErrorResult& aRv) |
|
5158 { |
|
5159 nsresult rv; |
|
5160 if (mRanges.Length() == 0 || !aNode) |
|
5161 return false; |
|
5162 |
|
5163 // XXXbz this duplicates the GetNodeLength code in nsRange.cpp |
|
5164 uint32_t nodeLength; |
|
5165 bool isData = aNode->IsNodeOfType(nsINode::eDATA_NODE); |
|
5166 if (isData) { |
|
5167 nodeLength = static_cast<nsIContent*>(aNode)->TextLength(); |
|
5168 } else { |
|
5169 nodeLength = aNode->GetChildCount(); |
|
5170 } |
|
5171 |
|
5172 nsTArray<nsRange*> overlappingRanges; |
|
5173 rv = GetRangesForIntervalArray(aNode, 0, aNode, nodeLength, |
|
5174 false, &overlappingRanges); |
|
5175 if (NS_FAILED(rv)) { |
|
5176 aRv.Throw(rv); |
|
5177 return false; |
|
5178 } |
|
5179 if (overlappingRanges.Length() == 0) |
|
5180 return false; // no ranges overlap |
|
5181 |
|
5182 // if the caller said partial intersections are OK, we're done |
|
5183 if (aAllowPartial) { |
|
5184 return true; |
|
5185 } |
|
5186 |
|
5187 // text nodes always count as inside |
|
5188 if (isData) { |
|
5189 return true; |
|
5190 } |
|
5191 |
|
5192 // The caller wants to know if the node is entirely within the given range, |
|
5193 // so we have to check all intersecting ranges. |
|
5194 for (uint32_t i = 0; i < overlappingRanges.Length(); i++) { |
|
5195 bool nodeStartsBeforeRange, nodeEndsAfterRange; |
|
5196 if (NS_SUCCEEDED(nsRange::CompareNodeToRange(aNode, overlappingRanges[i], |
|
5197 &nodeStartsBeforeRange, |
|
5198 &nodeEndsAfterRange))) { |
|
5199 if (!nodeStartsBeforeRange && !nodeEndsAfterRange) { |
|
5200 return true; |
|
5201 } |
|
5202 } |
|
5203 } |
|
5204 return false; |
|
5205 } |
|
5206 |
|
5207 |
|
5208 nsPresContext* |
|
5209 Selection::GetPresContext() const |
|
5210 { |
|
5211 nsIPresShell *shell = GetPresShell(); |
|
5212 if (!shell) { |
|
5213 return nullptr; |
|
5214 } |
|
5215 |
|
5216 return shell->GetPresContext(); |
|
5217 } |
|
5218 |
|
5219 nsIPresShell* |
|
5220 Selection::GetPresShell() const |
|
5221 { |
|
5222 if (!mFrameSelection) |
|
5223 return nullptr;//nothing to do |
|
5224 |
|
5225 return mFrameSelection->GetShell(); |
|
5226 } |
|
5227 |
|
5228 nsIFrame * |
|
5229 Selection::GetSelectionAnchorGeometry(SelectionRegion aRegion, nsRect* aRect) |
|
5230 { |
|
5231 if (!mFrameSelection) |
|
5232 return nullptr; // nothing to do |
|
5233 |
|
5234 NS_ENSURE_TRUE(aRect, nullptr); |
|
5235 |
|
5236 aRect->SetRect(0, 0, 0, 0); |
|
5237 |
|
5238 switch (aRegion) { |
|
5239 case nsISelectionController::SELECTION_ANCHOR_REGION: |
|
5240 case nsISelectionController::SELECTION_FOCUS_REGION: |
|
5241 return GetSelectionEndPointGeometry(aRegion, aRect); |
|
5242 break; |
|
5243 case nsISelectionController::SELECTION_WHOLE_SELECTION: |
|
5244 break; |
|
5245 default: |
|
5246 return nullptr; |
|
5247 } |
|
5248 |
|
5249 NS_ASSERTION(aRegion == nsISelectionController::SELECTION_WHOLE_SELECTION, |
|
5250 "should only be SELECTION_WHOLE_SELECTION here"); |
|
5251 |
|
5252 nsRect anchorRect; |
|
5253 nsIFrame* anchorFrame = GetSelectionEndPointGeometry( |
|
5254 nsISelectionController::SELECTION_ANCHOR_REGION, &anchorRect); |
|
5255 if (!anchorFrame) |
|
5256 return nullptr; |
|
5257 |
|
5258 nsRect focusRect; |
|
5259 nsIFrame* focusFrame = GetSelectionEndPointGeometry( |
|
5260 nsISelectionController::SELECTION_FOCUS_REGION, &focusRect); |
|
5261 if (!focusFrame) |
|
5262 return nullptr; |
|
5263 |
|
5264 NS_ASSERTION(anchorFrame->PresContext() == focusFrame->PresContext(), |
|
5265 "points of selection in different documents?"); |
|
5266 // make focusRect relative to anchorFrame |
|
5267 focusRect += focusFrame->GetOffsetTo(anchorFrame); |
|
5268 |
|
5269 aRect->UnionRectEdges(anchorRect, focusRect); |
|
5270 return anchorFrame; |
|
5271 } |
|
5272 |
|
5273 nsIFrame * |
|
5274 Selection::GetSelectionEndPointGeometry(SelectionRegion aRegion, nsRect* aRect) |
|
5275 { |
|
5276 if (!mFrameSelection) |
|
5277 return nullptr; // nothing to do |
|
5278 |
|
5279 NS_ENSURE_TRUE(aRect, nullptr); |
|
5280 |
|
5281 aRect->SetRect(0, 0, 0, 0); |
|
5282 |
|
5283 nsINode *node = nullptr; |
|
5284 uint32_t nodeOffset = 0; |
|
5285 nsIFrame *frame = nullptr; |
|
5286 |
|
5287 switch (aRegion) { |
|
5288 case nsISelectionController::SELECTION_ANCHOR_REGION: |
|
5289 node = GetAnchorNode(); |
|
5290 nodeOffset = AnchorOffset(); |
|
5291 break; |
|
5292 case nsISelectionController::SELECTION_FOCUS_REGION: |
|
5293 node = GetFocusNode(); |
|
5294 nodeOffset = FocusOffset(); |
|
5295 break; |
|
5296 default: |
|
5297 return nullptr; |
|
5298 } |
|
5299 |
|
5300 if (!node) |
|
5301 return nullptr; |
|
5302 |
|
5303 nsCOMPtr<nsIContent> content = do_QueryInterface(node); |
|
5304 NS_ENSURE_TRUE(content.get(), nullptr); |
|
5305 int32_t frameOffset = 0; |
|
5306 frame = mFrameSelection->GetFrameForNodeOffset(content, nodeOffset, |
|
5307 mFrameSelection->GetHint(), |
|
5308 &frameOffset); |
|
5309 if (!frame) |
|
5310 return nullptr; |
|
5311 |
|
5312 // Figure out what node type we have, then get the |
|
5313 // appropriate rect for it's nodeOffset. |
|
5314 bool isText = node->IsNodeOfType(nsINode::eTEXT); |
|
5315 |
|
5316 nsPoint pt(0, 0); |
|
5317 if (isText) { |
|
5318 nsIFrame* childFrame = nullptr; |
|
5319 frameOffset = 0; |
|
5320 nsresult rv = |
|
5321 frame->GetChildFrameContainingOffset(nodeOffset, |
|
5322 mFrameSelection->GetHint(), |
|
5323 &frameOffset, &childFrame); |
|
5324 if (NS_FAILED(rv)) |
|
5325 return nullptr; |
|
5326 if (!childFrame) |
|
5327 return nullptr; |
|
5328 |
|
5329 frame = childFrame; |
|
5330 |
|
5331 // Get the x coordinate of the offset into the text frame. |
|
5332 rv = GetCachedFrameOffset(frame, nodeOffset, pt); |
|
5333 if (NS_FAILED(rv)) |
|
5334 return nullptr; |
|
5335 } |
|
5336 |
|
5337 // Return the rect relative to the frame, with zero width. |
|
5338 if (isText) { |
|
5339 aRect->x = pt.x; |
|
5340 } else if (mFrameSelection->GetHint() == nsFrameSelection::HINTLEFT) { |
|
5341 // It's the frame's right edge we're interested in. |
|
5342 aRect->x = frame->GetRect().width; |
|
5343 } |
|
5344 aRect->height = frame->GetRect().height; |
|
5345 |
|
5346 return frame; |
|
5347 } |
|
5348 |
|
5349 NS_IMETHODIMP |
|
5350 Selection::ScrollSelectionIntoViewEvent::Run() |
|
5351 { |
|
5352 if (!mSelection) |
|
5353 return NS_OK; // event revoked |
|
5354 |
|
5355 int32_t flags = Selection::SCROLL_DO_FLUSH | |
|
5356 Selection::SCROLL_SYNCHRONOUS; |
|
5357 |
|
5358 mSelection->mScrollEvent.Forget(); |
|
5359 mSelection->ScrollIntoView(mRegion, mVerticalScroll, |
|
5360 mHorizontalScroll, mFlags | flags); |
|
5361 return NS_OK; |
|
5362 } |
|
5363 |
|
5364 nsresult |
|
5365 Selection::PostScrollSelectionIntoViewEvent( |
|
5366 SelectionRegion aRegion, |
|
5367 int32_t aFlags, |
|
5368 nsIPresShell::ScrollAxis aVertical, |
|
5369 nsIPresShell::ScrollAxis aHorizontal) |
|
5370 { |
|
5371 // If we've already posted an event, revoke it and place a new one at the |
|
5372 // end of the queue to make sure that any new pending reflow events are |
|
5373 // processed before we scroll. This will insure that we scroll to the |
|
5374 // correct place on screen. |
|
5375 mScrollEvent.Revoke(); |
|
5376 |
|
5377 nsRefPtr<ScrollSelectionIntoViewEvent> ev = |
|
5378 new ScrollSelectionIntoViewEvent(this, aRegion, aVertical, aHorizontal, |
|
5379 aFlags); |
|
5380 nsresult rv = NS_DispatchToCurrentThread(ev); |
|
5381 NS_ENSURE_SUCCESS(rv, rv); |
|
5382 |
|
5383 mScrollEvent = ev; |
|
5384 return NS_OK; |
|
5385 } |
|
5386 |
|
5387 NS_IMETHODIMP |
|
5388 Selection::ScrollIntoView(SelectionRegion aRegion, bool aIsSynchronous, |
|
5389 int16_t aVPercent, int16_t aHPercent) |
|
5390 { |
|
5391 ErrorResult result; |
|
5392 ScrollIntoView(aRegion, aIsSynchronous, aVPercent, aHPercent, result); |
|
5393 if (result.Failed()) { |
|
5394 return result.ErrorCode(); |
|
5395 } |
|
5396 return NS_OK; |
|
5397 } |
|
5398 |
|
5399 void |
|
5400 Selection::ScrollIntoView(int16_t aRegion, bool aIsSynchronous, |
|
5401 int16_t aVPercent, int16_t aHPercent, |
|
5402 ErrorResult& aRv) |
|
5403 { |
|
5404 nsresult rv = ScrollIntoViewInternal(aRegion, aIsSynchronous, |
|
5405 nsIPresShell::ScrollAxis(aVPercent), |
|
5406 nsIPresShell::ScrollAxis(aHPercent)); |
|
5407 if (NS_FAILED(rv)) { |
|
5408 aRv.Throw(rv); |
|
5409 } |
|
5410 } |
|
5411 |
|
5412 NS_IMETHODIMP |
|
5413 Selection::ScrollIntoViewInternal(SelectionRegion aRegion, bool aIsSynchronous, |
|
5414 nsIPresShell::ScrollAxis aVertical, |
|
5415 nsIPresShell::ScrollAxis aHorizontal) |
|
5416 { |
|
5417 return ScrollIntoView(aRegion, aVertical, aHorizontal, |
|
5418 aIsSynchronous ? Selection::SCROLL_SYNCHRONOUS : 0); |
|
5419 } |
|
5420 |
|
5421 nsresult |
|
5422 Selection::ScrollIntoView(SelectionRegion aRegion, |
|
5423 nsIPresShell::ScrollAxis aVertical, |
|
5424 nsIPresShell::ScrollAxis aHorizontal, |
|
5425 int32_t aFlags) |
|
5426 { |
|
5427 if (!mFrameSelection) |
|
5428 return NS_OK;//nothing to do |
|
5429 |
|
5430 nsCOMPtr<nsIPresShell> presShell = mFrameSelection->GetShell(); |
|
5431 if (!presShell) |
|
5432 return NS_OK; |
|
5433 |
|
5434 if (mFrameSelection->GetBatching()) |
|
5435 return NS_OK; |
|
5436 |
|
5437 if (!(aFlags & Selection::SCROLL_SYNCHRONOUS)) |
|
5438 return PostScrollSelectionIntoViewEvent(aRegion, aFlags, |
|
5439 aVertical, aHorizontal); |
|
5440 |
|
5441 // Now that text frame character offsets are always valid (though not |
|
5442 // necessarily correct), the worst that will happen if we don't flush here |
|
5443 // is that some callers might scroll to the wrong place. Those should |
|
5444 // either manually flush if they're in a safe position for it or use the |
|
5445 // async version of this method. |
|
5446 if (aFlags & Selection::SCROLL_DO_FLUSH) { |
|
5447 presShell->FlushPendingNotifications(Flush_Layout); |
|
5448 |
|
5449 // Reget the presshell, since it might have been Destroy'ed. |
|
5450 presShell = mFrameSelection ? mFrameSelection->GetShell() : nullptr; |
|
5451 if (!presShell) |
|
5452 return NS_OK; |
|
5453 } |
|
5454 |
|
5455 // |
|
5456 // Scroll the selection region into view. |
|
5457 // |
|
5458 |
|
5459 nsRect rect; |
|
5460 nsIFrame* frame = GetSelectionAnchorGeometry(aRegion, &rect); |
|
5461 if (!frame) |
|
5462 return NS_ERROR_FAILURE; |
|
5463 |
|
5464 // Scroll vertically to get the caret into view, but only if the container |
|
5465 // is perceived to be scrollable in that direction (i.e. there is a visible |
|
5466 // vertical scrollbar or the scroll range is at least one device pixel) |
|
5467 aVertical.mOnlyIfPerceivedScrollableDirection = true; |
|
5468 |
|
5469 uint32_t flags = 0; |
|
5470 if (aFlags & Selection::SCROLL_FIRST_ANCESTOR_ONLY) { |
|
5471 flags |= nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY; |
|
5472 } |
|
5473 if (aFlags & Selection::SCROLL_OVERFLOW_HIDDEN) { |
|
5474 flags |= nsIPresShell::SCROLL_OVERFLOW_HIDDEN; |
|
5475 } |
|
5476 |
|
5477 presShell->ScrollFrameRectIntoView(frame, rect, aVertical, aHorizontal, |
|
5478 flags); |
|
5479 return NS_OK; |
|
5480 } |
|
5481 |
|
5482 NS_IMETHODIMP |
|
5483 Selection::AddSelectionListener(nsISelectionListener* aNewListener) |
|
5484 { |
|
5485 if (!aNewListener) |
|
5486 return NS_ERROR_NULL_POINTER; |
|
5487 ErrorResult result; |
|
5488 AddSelectionListener(aNewListener, result); |
|
5489 if (result.Failed()) { |
|
5490 return result.ErrorCode(); |
|
5491 } |
|
5492 return NS_OK; |
|
5493 } |
|
5494 |
|
5495 void |
|
5496 Selection::AddSelectionListener(nsISelectionListener* aNewListener, |
|
5497 ErrorResult& aRv) |
|
5498 { |
|
5499 bool result = mSelectionListeners.AppendObject(aNewListener); // AddRefs |
|
5500 if (!result) { |
|
5501 aRv.Throw(NS_ERROR_FAILURE); |
|
5502 } |
|
5503 } |
|
5504 |
|
5505 NS_IMETHODIMP |
|
5506 Selection::RemoveSelectionListener(nsISelectionListener* aListenerToRemove) |
|
5507 { |
|
5508 if (!aListenerToRemove) |
|
5509 return NS_ERROR_NULL_POINTER; |
|
5510 ErrorResult result; |
|
5511 RemoveSelectionListener(aListenerToRemove, result); |
|
5512 if (result.Failed()) { |
|
5513 return result.ErrorCode(); |
|
5514 } |
|
5515 return NS_OK; |
|
5516 } |
|
5517 |
|
5518 void |
|
5519 Selection::RemoveSelectionListener(nsISelectionListener* aListenerToRemove, |
|
5520 ErrorResult& aRv) |
|
5521 { |
|
5522 bool result = mSelectionListeners.RemoveObject(aListenerToRemove); // Releases |
|
5523 if (!result) { |
|
5524 aRv.Throw(NS_ERROR_FAILURE); |
|
5525 } |
|
5526 } |
|
5527 |
|
5528 nsresult |
|
5529 Selection::NotifySelectionListeners() |
|
5530 { |
|
5531 if (!mFrameSelection) |
|
5532 return NS_OK;//nothing to do |
|
5533 |
|
5534 if (mFrameSelection->GetBatching()) { |
|
5535 mFrameSelection->SetDirty(); |
|
5536 return NS_OK; |
|
5537 } |
|
5538 nsCOMArray<nsISelectionListener> selectionListeners(mSelectionListeners); |
|
5539 int32_t cnt = selectionListeners.Count(); |
|
5540 if (cnt != mSelectionListeners.Count()) { |
|
5541 return NS_ERROR_OUT_OF_MEMORY; // nsCOMArray is fallible |
|
5542 } |
|
5543 |
|
5544 nsCOMPtr<nsIDOMDocument> domdoc; |
|
5545 nsIPresShell* ps = GetPresShell(); |
|
5546 if (ps) { |
|
5547 domdoc = do_QueryInterface(ps->GetDocument()); |
|
5548 } |
|
5549 |
|
5550 short reason = mFrameSelection->PopReason(); |
|
5551 for (int32_t i = 0; i < cnt; i++) { |
|
5552 selectionListeners[i]->NotifySelectionChanged(domdoc, this, reason); |
|
5553 } |
|
5554 return NS_OK; |
|
5555 } |
|
5556 |
|
5557 NS_IMETHODIMP |
|
5558 Selection::StartBatchChanges() |
|
5559 { |
|
5560 if (mFrameSelection) |
|
5561 mFrameSelection->StartBatchChanges(); |
|
5562 |
|
5563 return NS_OK; |
|
5564 } |
|
5565 |
|
5566 |
|
5567 |
|
5568 NS_IMETHODIMP |
|
5569 Selection::EndBatchChanges() |
|
5570 { |
|
5571 if (mFrameSelection) |
|
5572 mFrameSelection->EndBatchChanges(); |
|
5573 |
|
5574 return NS_OK; |
|
5575 } |
|
5576 |
|
5577 |
|
5578 |
|
5579 NS_IMETHODIMP |
|
5580 Selection::DeleteFromDocument() |
|
5581 { |
|
5582 ErrorResult result; |
|
5583 DeleteFromDocument(result); |
|
5584 return result.ErrorCode(); |
|
5585 } |
|
5586 |
|
5587 void |
|
5588 Selection::DeleteFromDocument(ErrorResult& aRv) |
|
5589 { |
|
5590 if (!mFrameSelection) |
|
5591 return;//nothing to do |
|
5592 nsresult rv = mFrameSelection->DeleteFromDocument(); |
|
5593 if (NS_FAILED(rv)) { |
|
5594 aRv.Throw(rv); |
|
5595 } |
|
5596 } |
|
5597 |
|
5598 NS_IMETHODIMP |
|
5599 Selection::Modify(const nsAString& aAlter, const nsAString& aDirection, |
|
5600 const nsAString& aGranularity) |
|
5601 { |
|
5602 ErrorResult result; |
|
5603 Modify(aAlter, aDirection, aGranularity, result); |
|
5604 return result.ErrorCode(); |
|
5605 } |
|
5606 |
|
5607 void |
|
5608 Selection::Modify(const nsAString& aAlter, const nsAString& aDirection, |
|
5609 const nsAString& aGranularity, ErrorResult& aRv) |
|
5610 { |
|
5611 // Silently exit if there's no selection or no focus node. |
|
5612 if (!mFrameSelection || !GetAnchorFocusRange() || !GetFocusNode()) { |
|
5613 return; |
|
5614 } |
|
5615 |
|
5616 if (!aAlter.LowerCaseEqualsLiteral("move") && |
|
5617 !aAlter.LowerCaseEqualsLiteral("extend")) { |
|
5618 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); |
|
5619 return; |
|
5620 } |
|
5621 |
|
5622 if (!aDirection.LowerCaseEqualsLiteral("forward") && |
|
5623 !aDirection.LowerCaseEqualsLiteral("backward") && |
|
5624 !aDirection.LowerCaseEqualsLiteral("left") && |
|
5625 !aDirection.LowerCaseEqualsLiteral("right")) { |
|
5626 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); |
|
5627 return; |
|
5628 } |
|
5629 |
|
5630 // Line moves are always visual. |
|
5631 bool visual = aDirection.LowerCaseEqualsLiteral("left") || |
|
5632 aDirection.LowerCaseEqualsLiteral("right") || |
|
5633 aGranularity.LowerCaseEqualsLiteral("line"); |
|
5634 |
|
5635 bool forward = aDirection.LowerCaseEqualsLiteral("forward") || |
|
5636 aDirection.LowerCaseEqualsLiteral("right"); |
|
5637 |
|
5638 bool extend = aAlter.LowerCaseEqualsLiteral("extend"); |
|
5639 |
|
5640 // The uint32_t casts below prevent an enum mismatch warning. |
|
5641 nsSelectionAmount amount; |
|
5642 uint32_t keycode; |
|
5643 if (aGranularity.LowerCaseEqualsLiteral("character")) { |
|
5644 amount = eSelectCluster; |
|
5645 keycode = forward ? (uint32_t) nsIDOMKeyEvent::DOM_VK_RIGHT : |
|
5646 (uint32_t) nsIDOMKeyEvent::DOM_VK_LEFT; |
|
5647 } |
|
5648 else if (aGranularity.LowerCaseEqualsLiteral("word")) { |
|
5649 amount = eSelectWordNoSpace; |
|
5650 keycode = forward ? (uint32_t) nsIDOMKeyEvent::DOM_VK_RIGHT : |
|
5651 (uint32_t) nsIDOMKeyEvent::DOM_VK_LEFT; |
|
5652 } |
|
5653 else if (aGranularity.LowerCaseEqualsLiteral("line")) { |
|
5654 amount = eSelectLine; |
|
5655 keycode = forward ? (uint32_t) nsIDOMKeyEvent::DOM_VK_DOWN : |
|
5656 (uint32_t) nsIDOMKeyEvent::DOM_VK_UP; |
|
5657 } |
|
5658 else if (aGranularity.LowerCaseEqualsLiteral("lineboundary")) { |
|
5659 amount = eSelectLine; |
|
5660 keycode = forward ? (uint32_t) nsIDOMKeyEvent::DOM_VK_END : |
|
5661 (uint32_t) nsIDOMKeyEvent::DOM_VK_HOME; |
|
5662 } |
|
5663 else if (aGranularity.LowerCaseEqualsLiteral("sentence") || |
|
5664 aGranularity.LowerCaseEqualsLiteral("sentenceboundary") || |
|
5665 aGranularity.LowerCaseEqualsLiteral("paragraph") || |
|
5666 aGranularity.LowerCaseEqualsLiteral("paragraphboundary") || |
|
5667 aGranularity.LowerCaseEqualsLiteral("documentboundary")) { |
|
5668 aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); |
|
5669 } |
|
5670 else { |
|
5671 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); |
|
5672 return; |
|
5673 } |
|
5674 |
|
5675 // If the anchor doesn't equal the focus and we try to move without first |
|
5676 // collapsing the selection, MoveCaret will collapse the selection and quit. |
|
5677 // To avoid this, we need to collapse the selection first. |
|
5678 nsresult rv = NS_OK; |
|
5679 if (!extend) { |
|
5680 nsINode* focusNode = GetFocusNode(); |
|
5681 // We should have checked earlier that there was a focus node. |
|
5682 if (!focusNode) { |
|
5683 aRv.Throw(NS_ERROR_UNEXPECTED); |
|
5684 return; |
|
5685 } |
|
5686 uint32_t focusOffset = FocusOffset(); |
|
5687 Collapse(focusNode, focusOffset); |
|
5688 } |
|
5689 |
|
5690 // If the base level of the focused frame is odd, we may have to swap the |
|
5691 // direction of the keycode. |
|
5692 nsIFrame *frame; |
|
5693 int32_t offset; |
|
5694 rv = GetPrimaryFrameForFocusNode(&frame, &offset, visual); |
|
5695 if (NS_SUCCEEDED(rv) && frame) { |
|
5696 nsBidiLevel baseLevel = nsBidiPresUtils::GetFrameBaseLevel(frame); |
|
5697 |
|
5698 if (baseLevel & 1) { |
|
5699 if (!visual && keycode == nsIDOMKeyEvent::DOM_VK_RIGHT) { |
|
5700 keycode = nsIDOMKeyEvent::DOM_VK_LEFT; |
|
5701 } |
|
5702 else if (!visual && keycode == nsIDOMKeyEvent::DOM_VK_LEFT) { |
|
5703 keycode = nsIDOMKeyEvent::DOM_VK_RIGHT; |
|
5704 } |
|
5705 else if (visual && keycode == nsIDOMKeyEvent::DOM_VK_HOME) { |
|
5706 keycode = nsIDOMKeyEvent::DOM_VK_END; |
|
5707 } |
|
5708 else if (visual && keycode == nsIDOMKeyEvent::DOM_VK_END) { |
|
5709 keycode = nsIDOMKeyEvent::DOM_VK_HOME; |
|
5710 } |
|
5711 } |
|
5712 } |
|
5713 |
|
5714 // MoveCaret will return an error if it can't move in the specified |
|
5715 // direction, but we just ignore this error unless it's a line move, in which |
|
5716 // case we call nsISelectionController::CompleteMove to move the cursor to |
|
5717 // the beginning/end of the line. |
|
5718 rv = mFrameSelection->MoveCaret(keycode, extend, amount, visual); |
|
5719 |
|
5720 if (aGranularity.LowerCaseEqualsLiteral("line") && NS_FAILED(rv)) { |
|
5721 nsCOMPtr<nsISelectionController> shell = |
|
5722 do_QueryInterface(mFrameSelection->GetShell()); |
|
5723 if (!shell) |
|
5724 return; |
|
5725 shell->CompleteMove(forward, extend); |
|
5726 } |
|
5727 } |
|
5728 |
|
5729 /** SelectionLanguageChange modifies the cursor Bidi level after a change in keyboard direction |
|
5730 * @param aLangRTL is true if the new language is right-to-left or false if the new language is left-to-right |
|
5731 */ |
|
5732 NS_IMETHODIMP |
|
5733 Selection::SelectionLanguageChange(bool aLangRTL) |
|
5734 { |
|
5735 if (!mFrameSelection) |
|
5736 return NS_ERROR_NOT_INITIALIZED; // Can't do selection |
|
5737 nsresult result; |
|
5738 nsIFrame *focusFrame = 0; |
|
5739 |
|
5740 result = GetPrimaryFrameForFocusNode(&focusFrame, nullptr, false); |
|
5741 if (NS_FAILED(result)) { |
|
5742 return result; |
|
5743 } |
|
5744 if (!focusFrame) { |
|
5745 return NS_ERROR_FAILURE; |
|
5746 } |
|
5747 |
|
5748 int32_t frameStart, frameEnd; |
|
5749 focusFrame->GetOffsets(frameStart, frameEnd); |
|
5750 nsRefPtr<nsPresContext> context = GetPresContext(); |
|
5751 uint8_t levelBefore, levelAfter; |
|
5752 if (!context) { |
|
5753 return NS_ERROR_FAILURE; |
|
5754 } |
|
5755 |
|
5756 uint8_t level = NS_GET_EMBEDDING_LEVEL(focusFrame); |
|
5757 int32_t focusOffset = static_cast<int32_t>(FocusOffset()); |
|
5758 if ((focusOffset != frameStart) && (focusOffset != frameEnd)) |
|
5759 // the cursor is not at a frame boundary, so the level of both the characters (logically) before and after the cursor |
|
5760 // is equal to the frame level |
|
5761 levelBefore = levelAfter = level; |
|
5762 else { |
|
5763 // the cursor is at a frame boundary, so use GetPrevNextBidiLevels to find the level of the characters |
|
5764 // before and after the cursor |
|
5765 nsCOMPtr<nsIContent> focusContent = do_QueryInterface(GetFocusNode()); |
|
5766 /* |
|
5767 nsFrameSelection::HINT hint; |
|
5768 |
|
5769 if ((focusOffset == frameStart && level) // beginning of an RTL frame |
|
5770 || (focusOffset == frameEnd && !level)) { // end of an LTR frame |
|
5771 hint = nsFrameSelection::HINTRIGHT; |
|
5772 } |
|
5773 else { // end of an RTL frame or beginning of an LTR frame |
|
5774 hint = nsFrameSelection::HINTLEFT; |
|
5775 } |
|
5776 mFrameSelection->SetHint(hint); |
|
5777 */ |
|
5778 nsPrevNextBidiLevels levels = mFrameSelection-> |
|
5779 GetPrevNextBidiLevels(focusContent, focusOffset, false); |
|
5780 |
|
5781 levelBefore = levels.mLevelBefore; |
|
5782 levelAfter = levels.mLevelAfter; |
|
5783 } |
|
5784 |
|
5785 if ((levelBefore & 1) == (levelAfter & 1)) { |
|
5786 // if cursor is between two characters with the same orientation, changing the keyboard language |
|
5787 // must toggle the cursor level between the level of the character with the lowest level |
|
5788 // (if the new language corresponds to the orientation of that character) and this level plus 1 |
|
5789 // (if the new language corresponds to the opposite orientation) |
|
5790 if ((level != levelBefore) && (level != levelAfter)) |
|
5791 level = std::min(levelBefore, levelAfter); |
|
5792 if ((level & 1) == aLangRTL) |
|
5793 mFrameSelection->SetCaretBidiLevel(level); |
|
5794 else |
|
5795 mFrameSelection->SetCaretBidiLevel(level + 1); |
|
5796 } |
|
5797 else { |
|
5798 // if cursor is between characters with opposite orientations, changing the keyboard language must change |
|
5799 // the cursor level to that of the adjacent character with the orientation corresponding to the new language. |
|
5800 if ((levelBefore & 1) == aLangRTL) |
|
5801 mFrameSelection->SetCaretBidiLevel(levelBefore); |
|
5802 else |
|
5803 mFrameSelection->SetCaretBidiLevel(levelAfter); |
|
5804 } |
|
5805 |
|
5806 // The caret might have moved, so invalidate the desired X position |
|
5807 // for future usages of up-arrow or down-arrow |
|
5808 mFrameSelection->InvalidateDesiredX(); |
|
5809 |
|
5810 return NS_OK; |
|
5811 } |
|
5812 |
|
5813 NS_IMETHODIMP_(nsDirection) |
|
5814 Selection::GetSelectionDirection() { |
|
5815 return mDirection; |
|
5816 } |
|
5817 |
|
5818 NS_IMETHODIMP_(void) |
|
5819 Selection::SetSelectionDirection(nsDirection aDirection) { |
|
5820 mDirection = aDirection; |
|
5821 } |
|
5822 |
|
5823 JSObject* |
|
5824 Selection::WrapObject(JSContext* aCx) |
|
5825 { |
|
5826 return mozilla::dom::SelectionBinding::Wrap(aCx, this); |
|
5827 } |
|
5828 |
|
5829 // nsAutoCopyListener |
|
5830 |
|
5831 nsAutoCopyListener* nsAutoCopyListener::sInstance = nullptr; |
|
5832 |
|
5833 NS_IMPL_ISUPPORTS(nsAutoCopyListener, nsISelectionListener) |
|
5834 |
|
5835 /* |
|
5836 * What we do now: |
|
5837 * On every selection change, we copy to the clipboard anew, creating a |
|
5838 * HTML buffer, a transferable, an nsISupportsString and |
|
5839 * a huge mess every time. This is basically what nsPresShell::DoCopy does |
|
5840 * to move the selection into the clipboard for Edit->Copy. |
|
5841 * |
|
5842 * What we should do, to make our end of the deal faster: |
|
5843 * Create a singleton transferable with our own magic converter. When selection |
|
5844 * changes (use a quick cache to detect ``real'' changes), we put the new |
|
5845 * nsISelection in the transferable. Our magic converter will take care of |
|
5846 * transferable->whatever-other-format when the time comes to actually |
|
5847 * hand over the clipboard contents. |
|
5848 * |
|
5849 * Other issues: |
|
5850 * - which X clipboard should we populate? |
|
5851 * - should we use a different one than Edit->Copy, so that inadvertant |
|
5852 * selections (or simple clicks, which currently cause a selection |
|
5853 * notification, regardless of if they're in the document which currently has |
|
5854 * selection!) don't lose the contents of the ``application''? Or should we |
|
5855 * just put some intelligence in the ``is this a real selection?'' code to |
|
5856 * protect our selection against clicks in other documents that don't create |
|
5857 * selections? |
|
5858 * - maybe we should just never clear the X clipboard? That would make this |
|
5859 * problem just go away, which is very tempting. |
|
5860 */ |
|
5861 |
|
5862 NS_IMETHODIMP |
|
5863 nsAutoCopyListener::NotifySelectionChanged(nsIDOMDocument *aDoc, |
|
5864 nsISelection *aSel, int16_t aReason) |
|
5865 { |
|
5866 if (!(aReason & nsISelectionListener::MOUSEUP_REASON || |
|
5867 aReason & nsISelectionListener::SELECTALL_REASON || |
|
5868 aReason & nsISelectionListener::KEYPRESS_REASON)) |
|
5869 return NS_OK; //dont care if we are still dragging |
|
5870 |
|
5871 bool collapsed; |
|
5872 if (!aDoc || !aSel || |
|
5873 NS_FAILED(aSel->GetIsCollapsed(&collapsed)) || collapsed) { |
|
5874 #ifdef DEBUG_CLIPBOARD |
|
5875 fprintf(stderr, "CLIPBOARD: no selection/collapsed selection\n"); |
|
5876 #endif |
|
5877 /* clear X clipboard? */ |
|
5878 return NS_OK; |
|
5879 } |
|
5880 |
|
5881 nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc); |
|
5882 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); |
|
5883 |
|
5884 // call the copy code |
|
5885 return nsCopySupport::HTMLCopy(aSel, doc, nsIClipboard::kSelectionClipboard); |
|
5886 } |