layout/generic/nsSelection.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial