Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/generic/nsFrameSelection.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
/*
8
 * Implementation of nsFrameSelection
9
 */
10
11
#include "nsFrameSelection.h"
12
13
#include "mozilla/Attributes.h"
14
#include "mozilla/AutoRestore.h"
15
#include "mozilla/EventStates.h"
16
#include "mozilla/HTMLEditor.h"
17
#include "mozilla/PresShell.h"
18
19
#include "nsCOMPtr.h"
20
#include "nsString.h"
21
#include "nsISelectionListener.h"
22
#include "nsContentCID.h"
23
#include "nsDeviceContext.h"
24
#include "nsIContent.h"
25
#include "nsRange.h"
26
#include "nsITableCellLayout.h"
27
#include "nsTArray.h"
28
#include "nsTableWrapperFrame.h"
29
#include "nsTableCellFrame.h"
30
#include "nsIScrollableFrame.h"
31
#include "nsCCUncollectableMarker.h"
32
#include "nsIContentIterator.h"
33
#include "nsIDocumentEncoder.h"
34
#include "nsTextFragment.h"
35
#include <algorithm>
36
#include "nsContentUtils.h"
37
#include "nsCSSFrameConstructor.h"
38
39
#include "nsGkAtoms.h"
40
#include "nsIFrameTraversal.h"
41
#include "nsLayoutUtils.h"
42
#include "nsLayoutCID.h"
43
#include "nsBidiPresUtils.h"
44
static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID);
45
#include "nsTextFrame.h"
46
47
#include "nsContentUtils.h"
48
#include "nsThreadUtils.h"
49
#include "mozilla/Preferences.h"
50
51
#include "nsPresContext.h"
52
#include "nsIPresShell.h"
53
#include "nsCaret.h"
54
55
#include "mozilla/MouseEvents.h"
56
#include "mozilla/TextEvents.h"
57
58
#include "nsITimer.h"
59
// notifications
60
#include "nsIDocument.h"
61
62
#include "nsISelectionController.h" //for the enums
63
#include "nsCopySupport.h"
64
#include "nsIClipboard.h"
65
#include "nsIFrameInlines.h"
66
67
#include "nsIBidiKeyboard.h"
68
69
#include "nsError.h"
70
#include "mozilla/AutoCopyListener.h"
71
#include "mozilla/dom/Element.h"
72
#include "mozilla/dom/Selection.h"
73
#include "mozilla/dom/ShadowRoot.h"
74
#include "mozilla/dom/Text.h"
75
#include "mozilla/ErrorResult.h"
76
#include "mozilla/dom/SelectionBinding.h"
77
#include "mozilla/AsyncEventDispatcher.h"
78
#include "mozilla/Telemetry.h"
79
80
#include "nsFocusManager.h"
81
#include "nsPIDOMWindow.h"
82
83
using namespace mozilla;
84
using namespace mozilla::dom;
85
86
//#define DEBUG_TABLE 1
87
88
static bool IsValidSelectionPoint(nsFrameSelection *aFrameSel, nsINode *aNode);
89
90
static nsAtom *GetTag(nsINode *aNode);
91
// returns the parent
92
static nsINode* ParentOffset(nsINode *aNode, int32_t *aChildOffset);
93
static nsINode* GetCellParent(nsINode *aDomNode);
94
95
#ifdef PRINT_RANGE
96
static void printRange(nsRange *aDomRange);
97
#define DEBUG_OUT_RANGE(x)  printRange(x)
98
#else
99
#define DEBUG_OUT_RANGE(x)
100
#endif // PRINT_RANGE
101
102
103
/******************************************************************************
104
 * nsPeekOffsetStruct
105
 ******************************************************************************/
106
107
//#define DEBUG_SELECTION // uncomment for printf describing every collapse and extend.
108
//#define DEBUG_NAVIGATION
109
110
111
//#define DEBUG_TABLE_SELECTION 1
112
113
nsPeekOffsetStruct::nsPeekOffsetStruct(nsSelectionAmount aAmount,
114
                                       nsDirection aDirection,
115
                                       int32_t aStartOffset,
116
                                       nsPoint aDesiredPos,
117
                                       bool aJumpLines,
118
                                       bool aScrollViewStop,
119
                                       bool aIsKeyboardSelect,
120
                                       bool aVisual,
121
                                       bool aExtend,
122
                                       EWordMovementType aWordMovementType)
123
  : mAmount(aAmount)
124
  , mDirection(aDirection)
125
  , mStartOffset(aStartOffset)
126
  , mDesiredPos(aDesiredPos)
127
  , mWordMovementType(aWordMovementType)
128
  , mJumpLines(aJumpLines)
129
  , mScrollViewStop(aScrollViewStop)
130
  , mIsKeyboardSelect(aIsKeyboardSelect)
131
  , mVisual(aVisual)
132
  , mExtend(aExtend)
133
  , mResultContent()
134
  , mResultFrame(nullptr)
135
  , mContentOffset(0)
136
  , mAttach(CARET_ASSOCIATE_BEFORE)
137
0
{
138
0
}
139
140
// Array which contains index of each SelecionType in Selection::mDOMSelections.
141
// For avoiding using if nor switch to retrieve the index, this needs to have
142
// -1 for SelectionTypes which won't be created its Selection instance.
143
static const int8_t kIndexOfSelections[] = {
144
  -1,    // SelectionType::eInvalid
145
  -1,    // SelectionType::eNone
146
  0,     // SelectionType::eNormal
147
  1,     // SelectionType::eSpellCheck
148
  2,     // SelectionType::eIMERawClause
149
  3,     // SelectionType::eIMESelectedRawClause
150
  4,     // SelectionType::eIMEConvertedClause
151
  5,     // SelectionType::eIMESelectedClause
152
  6,     // SelectionType::eAccessibility
153
  7,     // SelectionType::eFind
154
  8,     // SelectionType::eURLSecondary
155
  9,     // SelectionType::eURLStrikeout
156
};
157
158
inline int8_t
159
GetIndexFromSelectionType(SelectionType aSelectionType)
160
0
{
161
0
  // The enum value of eInvalid is -1 and the others are sequential value
162
0
  // starting from 0.  Therefore, |SelectionType + 1| is the index of
163
0
  // kIndexOfSelections.
164
0
  return kIndexOfSelections[static_cast<int8_t>(aSelectionType) + 1];
165
0
}
166
167
/*
168
The limiter is used specifically for the text areas and textfields
169
In that case it is the DIV tag that is anonymously created for the text
170
areas/fields.  Text nodes and BR nodes fall beneath it.  In the case of a
171
BR node the limiter will be the parent and the offset will point before or
172
after the BR node.  In the case of the text node the parent content is
173
the text node itself and the offset will be the exact character position.
174
The offset is not important to check for validity.  Simply look at the
175
passed in content.  If it equals the limiter then the selection point is valid.
176
If its parent it the limiter then the point is also valid.  In the case of
177
NO limiter all points are valid since you are in a topmost iframe. (browser
178
or composer)
179
*/
180
bool
181
IsValidSelectionPoint(nsFrameSelection *aFrameSel, nsINode *aNode)
182
0
{
183
0
  if (!aFrameSel || !aNode)
184
0
    return false;
185
0
186
0
  nsIContent *limiter = aFrameSel->GetLimiter();
187
0
  if (limiter && limiter != aNode && limiter != aNode->GetParent()) {
188
0
    //if newfocus == the limiter. that's ok. but if not there and not parent bad
189
0
    return false; //not in the right content. tLimiter said so
190
0
  }
191
0
192
0
  limiter = aFrameSel->GetAncestorLimiter();
193
0
  return !limiter || nsContentUtils::ContentIsDescendantOf(aNode, limiter);
194
0
}
195
196
namespace mozilla {
197
struct MOZ_RAII AutoPrepareFocusRange
198
{
199
  AutoPrepareFocusRange(Selection* aSelection,
200
                        bool aContinueSelection,
201
                        bool aMultipleSelection
202
                        MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
203
0
  {
204
0
    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
205
0
206
0
    if (aSelection->mRanges.Length() <= 1) {
207
0
      return;
208
0
    }
209
0
210
0
    if (aSelection->mFrameSelection->IsUserSelectionReason()) {
211
0
      mUserSelect.emplace(aSelection);
212
0
    }
213
0
    bool userSelection = aSelection->mUserInitiated;
214
0
215
0
    nsTArray<RangeData>& ranges = aSelection->mRanges;
216
0
    if (!userSelection ||
217
0
        (!aContinueSelection && aMultipleSelection)) {
218
0
      // Scripted command or the user is starting a new explicit multi-range
219
0
      // selection.
220
0
      for (RangeData& entry : ranges) {
221
0
        entry.mRange->SetIsGenerated(false);
222
0
      }
223
0
      return;
224
0
    }
225
0
226
0
    int16_t reason = aSelection->mFrameSelection->mSelectionChangeReason;
227
0
    bool isAnchorRelativeOp = (reason & (nsISelectionListener::DRAG_REASON |
228
0
                                         nsISelectionListener::MOUSEDOWN_REASON |
229
0
                                         nsISelectionListener::MOUSEUP_REASON |
230
0
                                         nsISelectionListener::COLLAPSETOSTART_REASON));
231
0
    if (!isAnchorRelativeOp) {
232
0
      return;
233
0
    }
234
0
235
0
    // This operation is against the anchor but our current mAnchorFocusRange
236
0
    // represents the focus in a multi-range selection.  The anchor from a user
237
0
    // perspective is the most distant generated range on the opposite side.
238
0
    // Find that range and make it the mAnchorFocusRange.
239
0
    const size_t len = ranges.Length();
240
0
    size_t newAnchorFocusIndex = size_t(-1);
241
0
    if (aSelection->GetDirection() == eDirNext) {
242
0
      for (size_t i = 0; i < len; ++i) {
243
0
        if (ranges[i].mRange->IsGenerated()) {
244
0
          newAnchorFocusIndex = i;
245
0
          break;
246
0
        }
247
0
      }
248
0
    } else {
249
0
      size_t i = len;
250
0
      while (i--) {
251
0
        if (ranges[i].mRange->IsGenerated()) {
252
0
          newAnchorFocusIndex = i;
253
0
          break;
254
0
        }
255
0
      }
256
0
    }
257
0
258
0
    if (newAnchorFocusIndex == size_t(-1)) {
259
0
      // There are no generated ranges - that's fine.
260
0
      return;
261
0
    }
262
0
263
0
    // Setup the new mAnchorFocusRange and mark the old one as generated.
264
0
    if (aSelection->mAnchorFocusRange) {
265
0
      aSelection->mAnchorFocusRange->SetIsGenerated(true);
266
0
    }
267
0
    nsRange* range = ranges[newAnchorFocusIndex].mRange;
268
0
    range->SetIsGenerated(false);
269
0
    aSelection->mAnchorFocusRange = range;
270
0
271
0
    // Remove all generated ranges (including the old mAnchorFocusRange).
272
0
    RefPtr<nsPresContext> presContext = aSelection->GetPresContext();
273
0
    size_t i = len;
274
0
    while (i--) {
275
0
      range = aSelection->mRanges[i].mRange;
276
0
      if (range->IsGenerated()) {
277
0
        range->SetSelection(nullptr);
278
0
        aSelection->SelectFrames(presContext, range, false);
279
0
        aSelection->mRanges.RemoveElementAt(i);
280
0
      }
281
0
    }
282
0
    if (aSelection->mFrameSelection) {
283
0
      aSelection->mFrameSelection->InvalidateDesiredPos();
284
0
    }
285
0
  }
286
287
  Maybe<Selection::AutoUserInitiated> mUserSelect;
288
  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
289
};
290
291
} // namespace mozilla
292
293
////////////BEGIN nsFrameSelection methods
294
295
nsFrameSelection::nsFrameSelection()
296
0
{
297
0
  for (size_t i = 0; i < ArrayLength(mDomSelections); i++) {
298
0
    mDomSelections[i] = new Selection(this);
299
0
    mDomSelections[i]->SetType(kPresentSelectionTypes[i]);
300
0
  }
301
0
302
#ifdef XP_MACOSX
303
  // On macOS, cache the current selection to send to service menu of macOS.
304
  bool enableAutoCopy = true;
305
  AutoCopyListener::Init(nsIClipboard::kSelectionCache);
306
#else // #ifdef XP_MACOSX
307
  // Check to see if the auto-copy pref is enabled and make the normal
308
0
  // Selection notifies auto-copy listener of its changes.
309
0
  bool enableAutoCopy = AutoCopyListener::IsPrefEnabled();
310
0
  if (enableAutoCopy) {
311
0
    AutoCopyListener::Init(nsIClipboard::kSelectionClipboard);
312
0
  }
313
0
#endif // #ifdef XP_MACOSX #else
314
0
315
0
  if (enableAutoCopy) {
316
0
    int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
317
0
    if (mDomSelections[index]) {
318
0
      mDomSelections[index]->NotifyAutoCopy();
319
0
    }
320
0
  }
321
0
}
322
323
nsFrameSelection::~nsFrameSelection()
324
0
{
325
0
}
326
327
NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameSelection)
328
329
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameSelection)
330
0
  for (size_t i = 0; i < ArrayLength(tmp->mDomSelections); ++i) {
331
0
    tmp->mDomSelections[i] = nullptr;
332
0
  }
333
0
334
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCellParent)
335
0
  tmp->mSelectingTableCellMode = TableSelection::None;
336
0
  tmp->mDragSelectingCells = false;
337
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartSelectedCell)
338
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndSelectedCell)
339
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAppendStartSelectedCell)
340
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnselectCellOnMouseUp)
341
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mMaintainRange)
342
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiter)
343
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAncestorLimiter)
344
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
345
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameSelection)
346
0
  if (tmp->mShell && tmp->mShell->GetDocument() &&
347
0
      nsCCUncollectableMarker::InGeneration(cb,
348
0
                                            tmp->mShell->GetDocument()->
349
0
                                              GetMarkedCCGeneration())) {
350
0
    return NS_SUCCESS_INTERRUPTED_TRAVERSE;
351
0
  }
352
0
  for (size_t i = 0; i < ArrayLength(tmp->mDomSelections); ++i) {
353
0
    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDomSelections[i])
354
0
  }
355
0
356
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCellParent)
357
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStartSelectedCell)
358
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndSelectedCell)
359
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAppendStartSelectedCell)
360
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnselectCellOnMouseUp)
361
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMaintainRange)
362
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiter)
363
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAncestorLimiter)
364
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
365
366
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsFrameSelection, AddRef)
367
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsFrameSelection, Release)
368
369
// Get the x (or y, in vertical writing mode) position requested
370
// by the Key Handling for line-up/down
371
nsresult
372
nsFrameSelection::FetchDesiredPos(nsPoint &aDesiredPos)
373
0
{
374
0
  if (!mShell) {
375
0
    NS_ERROR("fetch desired position failed");
376
0
    return NS_ERROR_FAILURE;
377
0
  }
378
0
  if (mDesiredPosSet) {
379
0
    aDesiredPos = mDesiredPos;
380
0
    return NS_OK;
381
0
  }
382
0
383
0
  RefPtr<nsCaret> caret = mShell->GetCaret();
384
0
  if (!caret) {
385
0
    return NS_ERROR_NULL_POINTER;
386
0
  }
387
0
388
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
389
0
  caret->SetSelection(mDomSelections[index]);
390
0
391
0
  nsRect coord;
392
0
  nsIFrame* caretFrame = caret->GetGeometry(&coord);
393
0
  if (!caretFrame) {
394
0
    return NS_ERROR_FAILURE;
395
0
  }
396
0
  nsPoint viewOffset(0, 0);
397
0
  nsView* view = nullptr;
398
0
  caretFrame->GetOffsetFromView(viewOffset, &view);
399
0
  if (view) {
400
0
    coord += viewOffset;
401
0
  }
402
0
  aDesiredPos = coord.TopLeft();
403
0
  return NS_OK;
404
0
}
405
406
void
407
nsFrameSelection::InvalidateDesiredPos() // do not listen to mDesiredPos;
408
                                         // you must get another.
409
0
{
410
0
  mDesiredPosSet = false;
411
0
}
412
413
void
414
nsFrameSelection::SetDesiredPos(nsPoint aPos)
415
0
{
416
0
  mDesiredPos = aPos;
417
0
  mDesiredPosSet = true;
418
0
}
419
420
nsresult
421
nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(nsIFrame* aFrame,
422
                                                        const nsPoint& aPoint,
423
                                                        nsIFrame** aRetFrame,
424
                                                        nsPoint& aRetPoint)
425
0
{
426
0
  //
427
0
  // The whole point of this method is to return a frame and point that
428
0
  // that lie within the same valid subtree as the anchor node's frame,
429
0
  // for use with the method GetContentAndOffsetsFromPoint().
430
0
  //
431
0
  // A valid subtree is defined to be one where all the content nodes in
432
0
  // the tree have a valid parent-child relationship.
433
0
  //
434
0
  // If the anchor frame and aFrame are in the same subtree, aFrame will
435
0
  // be returned in aRetFrame. If they are in different subtrees, we
436
0
  // return the frame for the root of the subtree.
437
0
  //
438
0
439
0
  if (!aFrame || !aRetFrame)
440
0
    return NS_ERROR_NULL_POINTER;
441
0
442
0
  *aRetFrame = aFrame;
443
0
  aRetPoint  = aPoint;
444
0
445
0
  //
446
0
  // Get the frame and content for the selection's anchor point!
447
0
  //
448
0
449
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
450
0
  if (!mDomSelections[index])
451
0
    return NS_ERROR_NULL_POINTER;
452
0
453
0
  nsCOMPtr<nsIContent> anchorContent =
454
0
    do_QueryInterface(mDomSelections[index]->GetAnchorNode());
455
0
  if (!anchorContent)
456
0
    return NS_ERROR_FAILURE;
457
0
458
0
  //
459
0
  // Now find the root of the subtree containing the anchor's content.
460
0
  //
461
0
462
0
  NS_ENSURE_STATE(mShell);
463
0
  nsIContent* anchorRoot = anchorContent->GetSelectionRootContent(mShell);
464
0
  NS_ENSURE_TRUE(anchorRoot, NS_ERROR_UNEXPECTED);
465
0
466
0
  //
467
0
  // Now find the root of the subtree containing aFrame's content.
468
0
  //
469
0
470
0
  nsIContent* content = aFrame->GetContent();
471
0
472
0
  if (content)
473
0
  {
474
0
    nsIContent* contentRoot = content->GetSelectionRootContent(mShell);
475
0
    NS_ENSURE_TRUE(contentRoot, NS_ERROR_UNEXPECTED);
476
0
477
0
    if (anchorRoot == contentRoot)
478
0
    {
479
0
      // If the aFrame's content isn't the capturing content, it should be
480
0
      // a descendant.  At this time, we can return simply.
481
0
      nsIContent* capturedContent = nsIPresShell::GetCapturingContent();
482
0
      if (capturedContent != content)
483
0
      {
484
0
        return NS_OK;
485
0
      }
486
0
487
0
      // Find the frame under the mouse cursor with the root frame.
488
0
      // At this time, don't use the anchor's frame because it may not have
489
0
      // fixed positioned frames.
490
0
      nsIFrame* rootFrame = mShell->GetRootFrame();
491
0
      nsPoint ptInRoot = aPoint + aFrame->GetOffsetTo(rootFrame);
492
0
      nsIFrame* cursorFrame =
493
0
        nsLayoutUtils::GetFrameForPoint(rootFrame, ptInRoot);
494
0
495
0
      // If the mouse cursor in on a frame which is descendant of same
496
0
      // selection root, we can expand the selection to the frame.
497
0
      if (cursorFrame && cursorFrame->PresShell() == mShell)
498
0
      {
499
0
        nsIContent* cursorContent = cursorFrame->GetContent();
500
0
        NS_ENSURE_TRUE(cursorContent, NS_ERROR_FAILURE);
501
0
        nsIContent* cursorContentRoot =
502
0
          cursorContent->GetSelectionRootContent(mShell);
503
0
        NS_ENSURE_TRUE(cursorContentRoot, NS_ERROR_UNEXPECTED);
504
0
        if (cursorContentRoot == anchorRoot)
505
0
        {
506
0
          *aRetFrame = cursorFrame;
507
0
          aRetPoint = aPoint + aFrame->GetOffsetTo(cursorFrame);
508
0
          return NS_OK;
509
0
        }
510
0
      }
511
0
      // Otherwise, e.g., the cursor isn't on any frames (e.g., the mouse
512
0
      // cursor is out of the window), we should use the frame of the anchor
513
0
      // root.
514
0
    }
515
0
  }
516
0
517
0
  //
518
0
  // When we can't find a frame which is under the mouse cursor and has a same
519
0
  // selection root as the anchor node's, we should return the selection root
520
0
  // frame.
521
0
  //
522
0
523
0
  *aRetFrame = anchorRoot->GetPrimaryFrame();
524
0
525
0
  if (!*aRetFrame)
526
0
    return NS_ERROR_FAILURE;
527
0
528
0
  //
529
0
  // Now make sure that aRetPoint is converted to the same coordinate
530
0
  // system used by aRetFrame.
531
0
  //
532
0
533
0
  aRetPoint = aPoint + aFrame->GetOffsetTo(*aRetFrame);
534
0
535
0
  return NS_OK;
536
0
}
537
538
void
539
nsFrameSelection::SetCaretBidiLevel(nsBidiLevel aLevel)
540
0
{
541
0
  // If the current level is undefined, we have just inserted new text.
542
0
  // In this case, we don't want to reset the keyboard language
543
0
  mCaretBidiLevel = aLevel;
544
0
545
0
  RefPtr<nsCaret> caret;
546
0
  if (mShell && (caret = mShell->GetCaret())) {
547
0
    caret->SchedulePaint();
548
0
  }
549
0
}
550
551
nsBidiLevel
552
nsFrameSelection::GetCaretBidiLevel() const
553
0
{
554
0
  return mCaretBidiLevel;
555
0
}
556
557
void
558
nsFrameSelection::UndefineCaretBidiLevel()
559
0
{
560
0
  mCaretBidiLevel |= BIDI_LEVEL_UNDEFINED;
561
0
}
562
563
#ifdef PRINT_RANGE
564
void printRange(nsRange *aDomRange)
565
{
566
  if (!aDomRange)
567
  {
568
    printf("NULL Range\n");
569
  }
570
  nsINode* startNode = aDomRange->GetStartContainer();
571
  nsINode* endNode = aDomRange->GetEndContainer();
572
  int32_t startOffset = aDomRange->StartOffset();
573
  int32_t endOffset = aDomRange->EndOffset();
574
575
  printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n",
576
         (unsigned long)aDomRange,
577
         (unsigned long)startNode, (long)startOffset,
578
         (unsigned long)endNode, (long)endOffset);
579
580
}
581
#endif /* PRINT_RANGE */
582
583
static
584
nsAtom *GetTag(nsINode *aNode)
585
0
{
586
0
  nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
587
0
  if (!content)
588
0
  {
589
0
    MOZ_ASSERT_UNREACHABLE("bad node passed to GetTag()");
590
0
    return nullptr;
591
0
  }
592
0
593
0
  return content->NodeInfo()->NameAtom();
594
0
}
595
596
// Returns the parent
597
nsINode*
598
ParentOffset(nsINode *aNode, int32_t *aChildOffset)
599
0
{
600
0
  if (!aNode || !aChildOffset)
601
0
    return nullptr;
602
0
603
0
  nsIContent* parent = aNode->GetParent();
604
0
  if (parent)
605
0
  {
606
0
    *aChildOffset = parent->ComputeIndexOf(aNode);
607
0
608
0
    return parent;
609
0
  }
610
0
611
0
  return nullptr;
612
0
}
613
614
static nsINode*
615
GetCellParent(nsINode *aDomNode)
616
0
{
617
0
    if (!aDomNode)
618
0
      return nullptr;
619
0
    nsINode* current = aDomNode;
620
0
    // Start with current node and look for a table cell
621
0
    while (current)
622
0
    {
623
0
      nsAtom* tag = GetTag(current);
624
0
      if (tag == nsGkAtoms::td || tag == nsGkAtoms::th)
625
0
        return current;
626
0
      current = current->GetParent();
627
0
    }
628
0
    return nullptr;
629
0
}
630
631
void
632
nsFrameSelection::Init(nsIPresShell *aShell, nsIContent *aLimiter,
633
                       bool aAccessibleCaretEnabled)
634
0
{
635
0
  mShell = aShell;
636
0
  mDragState = false;
637
0
  mDesiredPosSet = false;
638
0
  mLimiter = aLimiter;
639
0
  mCaretMovementStyle =
640
0
    Preferences::GetInt("bidi.edit.caret_movement_style", 2);
641
0
642
0
  // This should only ever be initialized on the main thread, so we are OK here.
643
0
  static bool prefCachesInitialized = false;
644
0
  if (!prefCachesInitialized) {
645
0
    prefCachesInitialized = true;
646
0
647
0
    Preferences::AddBoolVarCache(&sSelectionEventsEnabled,
648
0
                                 "dom.select_events.enabled", false);
649
0
    Preferences::AddBoolVarCache(&sSelectionEventsOnTextControlsEnabled,
650
0
                                 "dom.select_events.textcontrols.enabled", false);
651
0
  }
652
0
653
0
  mAccessibleCaretEnabled = aAccessibleCaretEnabled;
654
0
  if (mAccessibleCaretEnabled) {
655
0
    int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
656
0
    mDomSelections[index]->MaybeNotifyAccessibleCaretEventHub(aShell);
657
0
  }
658
0
659
0
  bool plaintextControl = (aLimiter != nullptr);
660
0
  bool initSelectEvents = plaintextControl ?
661
0
                            sSelectionEventsOnTextControlsEnabled :
662
0
                            sSelectionEventsEnabled;
663
0
664
0
  nsIDocument* doc = aShell->GetDocument();
665
0
  if (initSelectEvents ||
666
0
      (doc && nsContentUtils::IsSystemPrincipal(doc->NodePrincipal()))) {
667
0
    int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
668
0
    if (mDomSelections[index]) {
669
0
      mDomSelections[index]->EnableSelectionChangeEvent();
670
0
    }
671
0
  }
672
0
}
673
674
bool nsFrameSelection::sSelectionEventsEnabled = false;
675
bool nsFrameSelection::sSelectionEventsOnTextControlsEnabled = false;
676
677
nsresult
678
nsFrameSelection::MoveCaret(nsDirection       aDirection,
679
                            bool              aContinueSelection,
680
                            nsSelectionAmount aAmount,
681
                            CaretMovementStyle aMovementStyle)
682
0
{
683
0
  bool visualMovement = aMovementStyle == eVisual ||
684
0
    (aMovementStyle == eUsePrefStyle &&
685
0
      (mCaretMovementStyle == 1 ||
686
0
        (mCaretMovementStyle == 2 && !aContinueSelection)));
687
0
688
0
  NS_ENSURE_STATE(mShell);
689
0
  // Flush out layout, since we need it to be up to date to do caret
690
0
  // positioning.
691
0
  mShell->FlushPendingNotifications(FlushType::Layout);
692
0
693
0
  if (!mShell) {
694
0
    return NS_OK;
695
0
  }
696
0
697
0
  nsPresContext *context = mShell->GetPresContext();
698
0
  if (!context)
699
0
    return NS_ERROR_FAILURE;
700
0
701
0
  nsPoint desiredPos(0, 0); //we must keep this around and revalidate it when its just UP/DOWN
702
0
703
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
704
0
  RefPtr<Selection> sel = mDomSelections[index];
705
0
  if (!sel)
706
0
    return NS_ERROR_NULL_POINTER;
707
0
708
0
  int32_t scrollFlags = Selection::SCROLL_FOR_CARET_MOVE;
709
0
  nsINode* focusNode = sel->GetFocusNode();
710
0
  if (focusNode &&
711
0
      (focusNode->IsEditable() ||
712
0
       (focusNode->IsElement() &&
713
0
        focusNode->AsElement()->State().
714
0
          HasState(NS_EVENT_STATE_MOZ_READWRITE)))) {
715
0
    // If caret moves in editor, it should cause scrolling even if it's in
716
0
    // overflow: hidden;.
717
0
    scrollFlags |= Selection::SCROLL_OVERFLOW_HIDDEN;
718
0
  }
719
0
720
0
  int32_t caretStyle = Preferences::GetInt("layout.selection.caret_style", 0);
721
0
  if (caretStyle == 0
722
#ifdef XP_WIN
723
      && aAmount != eSelectLine
724
#endif
725
0
     ) {
726
0
    // Put caret at the selection edge in the |aDirection| direction.
727
0
    caretStyle = 2;
728
0
  }
729
0
730
0
  bool doCollapse = !sel->IsCollapsed() && !aContinueSelection && caretStyle == 2 &&
731
0
                    aAmount <= eSelectLine;
732
0
  if (doCollapse) {
733
0
    if (aDirection == eDirPrevious) {
734
0
      PostReason(nsISelectionListener::COLLAPSETOSTART_REASON);
735
0
      mHint = CARET_ASSOCIATE_AFTER;
736
0
    } else {
737
0
      PostReason(nsISelectionListener::COLLAPSETOEND_REASON);
738
0
      mHint = CARET_ASSOCIATE_BEFORE;
739
0
    }
740
0
  } else {
741
0
    PostReason(nsISelectionListener::KEYPRESS_REASON);
742
0
  }
743
0
744
0
  AutoPrepareFocusRange prep(sel, aContinueSelection, false);
745
0
746
0
  if (aAmount == eSelectLine) {
747
0
    nsresult result = FetchDesiredPos(desiredPos);
748
0
    if (NS_FAILED(result)) {
749
0
      return result;
750
0
    }
751
0
    SetDesiredPos(desiredPos);
752
0
  }
753
0
754
0
  if (doCollapse) {
755
0
    const nsRange* anchorFocusRange = sel->GetAnchorFocusRange();
756
0
    if (anchorFocusRange) {
757
0
      nsINode* node;
758
0
      int32_t offset;
759
0
      if (aDirection == eDirPrevious) {
760
0
        node =  anchorFocusRange->GetStartContainer();
761
0
        offset = anchorFocusRange->StartOffset();
762
0
      } else {
763
0
        node = anchorFocusRange->GetEndContainer();
764
0
        offset = anchorFocusRange->EndOffset();
765
0
      }
766
0
      sel->Collapse(node, offset);
767
0
    }
768
0
    sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
769
0
                        nsIPresShell::ScrollAxis(),
770
0
                        nsIPresShell::ScrollAxis(), scrollFlags);
771
0
    return NS_OK;
772
0
  }
773
0
774
0
  nsIFrame *frame;
775
0
  int32_t offsetused = 0;
776
0
  nsresult result = sel->GetPrimaryFrameForFocusNode(&frame, &offsetused,
777
0
                                                     visualMovement);
778
0
779
0
  if (NS_FAILED(result) || !frame)
780
0
    return NS_FAILED(result) ? result : NS_ERROR_FAILURE;
781
0
782
0
  //set data using mLimiter to stop on scroll views.  If we have a limiter then we stop peeking
783
0
  //when we hit scrollable views.  If no limiter then just let it go ahead
784
0
  nsPeekOffsetStruct pos(aAmount, eDirPrevious, offsetused, desiredPos,
785
0
                         true, mLimiter != nullptr, true, visualMovement,
786
0
                         aContinueSelection);
787
0
788
0
  nsBidiDirection paraDir = nsBidiPresUtils::ParagraphDirection(frame);
789
0
790
0
  CaretAssociateHint tHint(mHint); //temporary variable so we dont set mHint until it is necessary
791
0
  switch (aAmount){
792
0
   case eSelectCharacter:
793
0
    case eSelectCluster:
794
0
    case eSelectWord:
795
0
    case eSelectWordNoSpace:
796
0
      InvalidateDesiredPos();
797
0
      pos.mAmount = aAmount;
798
0
      pos.mDirection = (visualMovement && paraDir == NSBIDI_RTL)
799
0
                       ? nsDirection(1 - aDirection) : aDirection;
800
0
      break;
801
0
    case eSelectLine:
802
0
      pos.mAmount = aAmount;
803
0
      pos.mDirection = aDirection;
804
0
      break;
805
0
    case eSelectBeginLine:
806
0
    case eSelectEndLine:
807
0
      InvalidateDesiredPos();
808
0
      pos.mAmount = aAmount;
809
0
      pos.mDirection = (visualMovement && paraDir == NSBIDI_RTL)
810
0
                       ? nsDirection(1 - aDirection) : aDirection;
811
0
      break;
812
0
    default:
813
0
      return NS_ERROR_FAILURE;
814
0
  }
815
0
816
0
  if (NS_SUCCEEDED(result = frame->PeekOffset(&pos)) && pos.mResultContent)
817
0
  {
818
0
    nsIFrame *theFrame;
819
0
    int32_t currentOffset, frameStart, frameEnd;
820
0
821
0
    if (aAmount <= eSelectWordNoSpace)
822
0
    {
823
0
      // For left/right, PeekOffset() sets pos.mResultFrame correctly, but does not set pos.mAttachForward,
824
0
      // so determine the hint here based on the result frame and offset:
825
0
      // If we're at the end of a text frame, set the hint to ASSOCIATE_BEFORE to indicate that we
826
0
      // want the caret displayed at the end of this frame, not at the beginning of the next one.
827
0
      theFrame = pos.mResultFrame;
828
0
      theFrame->GetOffsets(frameStart, frameEnd);
829
0
      currentOffset = pos.mContentOffset;
830
0
      if (frameEnd == currentOffset && !(frameStart == 0 && frameEnd == 0))
831
0
        tHint = CARET_ASSOCIATE_BEFORE;
832
0
      else
833
0
        tHint = CARET_ASSOCIATE_AFTER;
834
0
    } else {
835
0
      // For up/down and home/end, pos.mResultFrame might not be set correctly, or not at all.
836
0
      // In these cases, get the frame based on the content and hint returned by PeekOffset().
837
0
      tHint = pos.mAttach;
838
0
      theFrame = GetFrameForNodeOffset(pos.mResultContent, pos.mContentOffset,
839
0
                                       tHint, &currentOffset);
840
0
      if (!theFrame)
841
0
        return NS_ERROR_FAILURE;
842
0
843
0
      theFrame->GetOffsets(frameStart, frameEnd);
844
0
    }
845
0
846
0
    if (context->BidiEnabled())
847
0
    {
848
0
      switch (aAmount) {
849
0
        case eSelectBeginLine:
850
0
        case eSelectEndLine: {
851
0
          // In Bidi contexts, PeekOffset calculates pos.mContentOffset
852
0
          // differently depending on whether the movement is visual or logical.
853
0
          // For visual movement, pos.mContentOffset depends on the direction-
854
0
          // ality of the first/last frame on the line (theFrame), and the caret
855
0
          // directionality must correspond.
856
0
          FrameBidiData bidiData = theFrame->GetBidiData();
857
0
          SetCaretBidiLevel(visualMovement ? bidiData.embeddingLevel
858
0
                                           : bidiData.baseLevel);
859
0
          break;
860
0
        }
861
0
        default:
862
0
          // If the current position is not a frame boundary, it's enough just
863
0
          // to take the Bidi level of the current frame
864
0
          if ((pos.mContentOffset != frameStart &&
865
0
               pos.mContentOffset != frameEnd) ||
866
0
              eSelectLine == aAmount) {
867
0
            SetCaretBidiLevel(theFrame->GetEmbeddingLevel());
868
0
          }
869
0
          else {
870
0
            BidiLevelFromMove(mShell, pos.mResultContent, pos.mContentOffset,
871
0
                              aAmount, tHint);
872
0
          }
873
0
      }
874
0
    }
875
0
    result = TakeFocus(pos.mResultContent, pos.mContentOffset, pos.mContentOffset,
876
0
                       tHint, aContinueSelection, false);
877
0
  } else if (aAmount <= eSelectWordNoSpace && aDirection == eDirNext &&
878
0
             !aContinueSelection) {
879
0
    // Collapse selection if PeekOffset failed, we either
880
0
    //  1. bumped into the BRFrame, bug 207623
881
0
    //  2. had select-all in a text input (DIV range), bug 352759.
882
0
    bool isBRFrame = frame->IsBrFrame();
883
0
    sel->Collapse(sel->GetFocusNode(), sel->FocusOffset());
884
0
    // Note: 'frame' might be dead here.
885
0
    if (!isBRFrame) {
886
0
      mHint = CARET_ASSOCIATE_BEFORE; // We're now at the end of the frame to the left.
887
0
    }
888
0
    result = NS_OK;
889
0
  }
890
0
  if (NS_SUCCEEDED(result))
891
0
  {
892
0
    result = mDomSelections[index]->
893
0
      ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
894
0
                     nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(),
895
0
                     scrollFlags);
896
0
  }
897
0
898
0
  return result;
899
0
}
900
901
nsPrevNextBidiLevels
902
nsFrameSelection::GetPrevNextBidiLevels(nsIContent *aNode,
903
                                        uint32_t    aContentOffset,
904
                                        bool        aJumpLines) const
905
0
{
906
0
  return GetPrevNextBidiLevels(aNode, aContentOffset, mHint, aJumpLines);
907
0
}
908
909
nsPrevNextBidiLevels
910
nsFrameSelection::GetPrevNextBidiLevels(nsIContent*        aNode,
911
                                        uint32_t           aContentOffset,
912
                                        CaretAssociateHint aHint,
913
                                        bool               aJumpLines) const
914
0
{
915
0
  // Get the level of the frames on each side
916
0
  nsIFrame    *currentFrame;
917
0
  int32_t     currentOffset;
918
0
  int32_t     frameStart, frameEnd;
919
0
  nsDirection direction;
920
0
921
0
  nsPrevNextBidiLevels levels;
922
0
  levels.SetData(nullptr, nullptr, 0, 0);
923
0
924
0
  currentFrame = GetFrameForNodeOffset(aNode, aContentOffset,
925
0
                                       aHint, &currentOffset);
926
0
  if (!currentFrame)
927
0
    return levels;
928
0
929
0
  currentFrame->GetOffsets(frameStart, frameEnd);
930
0
931
0
  if (0 == frameStart && 0 == frameEnd)
932
0
    direction = eDirPrevious;
933
0
  else if (frameStart == currentOffset)
934
0
    direction = eDirPrevious;
935
0
  else if (frameEnd == currentOffset)
936
0
    direction = eDirNext;
937
0
  else {
938
0
    // we are neither at the beginning nor at the end of the frame, so we have no worries
939
0
    nsBidiLevel currentLevel = currentFrame->GetEmbeddingLevel();
940
0
    levels.SetData(currentFrame, currentFrame, currentLevel, currentLevel);
941
0
    return levels;
942
0
  }
943
0
944
0
  nsIFrame *newFrame;
945
0
  int32_t offset;
946
0
  bool jumpedLine, movedOverNonSelectableText;
947
0
  nsresult rv = currentFrame->GetFrameFromDirection(direction, false,
948
0
                                                    aJumpLines, true,
949
0
                                                    &newFrame, &offset, &jumpedLine,
950
0
                                                    &movedOverNonSelectableText);
951
0
  if (NS_FAILED(rv))
952
0
    newFrame = nullptr;
953
0
954
0
  FrameBidiData currentBidi = currentFrame->GetBidiData();
955
0
  nsBidiLevel currentLevel = currentBidi.embeddingLevel;
956
0
  nsBidiLevel newLevel = newFrame ? newFrame->GetEmbeddingLevel()
957
0
                                  : currentBidi.baseLevel;
958
0
959
0
  // If not jumping lines, disregard br frames, since they might be positioned incorrectly.
960
0
  // XXX This could be removed once bug 339786 is fixed.
961
0
  if (!aJumpLines) {
962
0
    if (currentFrame->IsBrFrame()) {
963
0
      currentFrame = nullptr;
964
0
      currentLevel = currentBidi.baseLevel;
965
0
    }
966
0
    if (newFrame && newFrame->IsBrFrame()) {
967
0
      newFrame = nullptr;
968
0
      newLevel = currentBidi.baseLevel;
969
0
    }
970
0
  }
971
0
972
0
  if (direction == eDirNext)
973
0
    levels.SetData(currentFrame, newFrame, currentLevel, newLevel);
974
0
  else
975
0
    levels.SetData(newFrame, currentFrame, newLevel, currentLevel);
976
0
977
0
  return levels;
978
0
}
979
980
nsresult
981
nsFrameSelection::GetFrameFromLevel(nsIFrame    *aFrameIn,
982
                                    nsDirection  aDirection,
983
                                    nsBidiLevel  aBidiLevel,
984
                                    nsIFrame   **aFrameOut) const
985
0
{
986
0
  NS_ENSURE_STATE(mShell);
987
0
  nsBidiLevel foundLevel = 0;
988
0
  nsIFrame *foundFrame = aFrameIn;
989
0
990
0
  nsCOMPtr<nsIFrameEnumerator> frameTraversal;
991
0
  nsresult result;
992
0
  nsCOMPtr<nsIFrameTraversal> trav(do_CreateInstance(kFrameTraversalCID,&result));
993
0
  if (NS_FAILED(result))
994
0
      return result;
995
0
996
0
  result = trav->NewFrameTraversal(getter_AddRefs(frameTraversal),
997
0
                                   mShell->GetPresContext(), aFrameIn,
998
0
                                   eLeaf,
999
0
                                   false, // aVisual
1000
0
                                   false, // aLockInScrollView
1001
0
                                   false, // aFollowOOFs
1002
0
                                   false  // aSkipPopupChecks
1003
0
                                   );
1004
0
  if (NS_FAILED(result))
1005
0
    return result;
1006
0
1007
0
  do {
1008
0
    *aFrameOut = foundFrame;
1009
0
    if (aDirection == eDirNext)
1010
0
      frameTraversal->Next();
1011
0
    else
1012
0
      frameTraversal->Prev();
1013
0
1014
0
    foundFrame = frameTraversal->CurrentItem();
1015
0
    if (!foundFrame)
1016
0
      return NS_ERROR_FAILURE;
1017
0
    foundLevel = foundFrame->GetEmbeddingLevel();
1018
0
1019
0
  } while (foundLevel > aBidiLevel);
1020
0
1021
0
  return NS_OK;
1022
0
}
1023
1024
1025
nsresult
1026
nsFrameSelection::MaintainSelection(nsSelectionAmount aAmount)
1027
0
{
1028
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
1029
0
  if (!mDomSelections[index])
1030
0
    return NS_ERROR_NULL_POINTER;
1031
0
1032
0
  mMaintainedAmount = aAmount;
1033
0
1034
0
  const nsRange* anchorFocusRange =
1035
0
    mDomSelections[index]->GetAnchorFocusRange();
1036
0
  if (anchorFocusRange && aAmount != eSelectNoAmount) {
1037
0
    mMaintainRange = anchorFocusRange->CloneRange();
1038
0
    return NS_OK;
1039
0
  }
1040
0
1041
0
  mMaintainRange = nullptr;
1042
0
  return NS_OK;
1043
0
}
1044
1045
1046
/** After moving the caret, its Bidi level is set according to the following rules:
1047
 *
1048
 *  After moving over a character with left/right arrow, set to the Bidi level of the last moved over character.
1049
 *  After Home and End, set to the paragraph embedding level.
1050
 *  After up/down arrow, PageUp/Down, set to the lower level of the 2 surrounding characters.
1051
 *  After mouse click, set to the level of the current frame.
1052
 *
1053
 *  The following two methods use GetPrevNextBidiLevels to determine the new Bidi level.
1054
 *  BidiLevelFromMove is called when the caret is moved in response to a keyboard event
1055
 *
1056
 * @param aPresShell is the presentation shell
1057
 * @param aNode is the content node
1058
 * @param aContentOffset is the new caret position, as an offset into aNode
1059
 * @param aAmount is the amount of the move that gave the caret its new position
1060
 * @param aHint is the hint indicating in what logical direction the caret moved
1061
 */
1062
void nsFrameSelection::BidiLevelFromMove(nsIPresShell*      aPresShell,
1063
                                         nsIContent*        aNode,
1064
                                         uint32_t           aContentOffset,
1065
                                         nsSelectionAmount  aAmount,
1066
                                         CaretAssociateHint aHint)
1067
0
{
1068
0
  switch (aAmount) {
1069
0
1070
0
    // Movement within the line: the new cursor Bidi level is the level of the
1071
0
    // last character moved over
1072
0
    case eSelectCharacter:
1073
0
    case eSelectCluster:
1074
0
    case eSelectWord:
1075
0
    case eSelectWordNoSpace:
1076
0
    case eSelectBeginLine:
1077
0
    case eSelectEndLine:
1078
0
    case eSelectNoAmount:
1079
0
    {
1080
0
      nsPrevNextBidiLevels levels = GetPrevNextBidiLevels(aNode, aContentOffset,
1081
0
                                                          aHint, false);
1082
0
1083
0
      SetCaretBidiLevel(aHint == CARET_ASSOCIATE_BEFORE ?
1084
0
          levels.mLevelBefore : levels.mLevelAfter);
1085
0
      break;
1086
0
    }
1087
0
      /*
1088
0
    // Up and Down: the new cursor Bidi level is the smaller of the two surrounding characters
1089
0
    case eSelectLine:
1090
0
    case eSelectParagraph:
1091
0
      GetPrevNextBidiLevels(aContext, aNode, aContentOffset, &firstFrame, &secondFrame, &firstLevel, &secondLevel);
1092
0
      aPresShell->SetCaretBidiLevel(std::min(firstLevel, secondLevel));
1093
0
      break;
1094
0
      */
1095
0
1096
0
    default:
1097
0
      UndefineCaretBidiLevel();
1098
0
  }
1099
0
}
1100
1101
/**
1102
 * BidiLevelFromClick is called when the caret is repositioned by clicking the mouse
1103
 *
1104
 * @param aNode is the content node
1105
 * @param aContentOffset is the new caret position, as an offset into aNode
1106
 */
1107
void nsFrameSelection::BidiLevelFromClick(nsIContent *aNode,
1108
                                          uint32_t    aContentOffset)
1109
0
{
1110
0
  nsIFrame* clickInFrame=nullptr;
1111
0
  int32_t OffsetNotUsed;
1112
0
1113
0
  clickInFrame = GetFrameForNodeOffset(aNode, aContentOffset, mHint, &OffsetNotUsed);
1114
0
  if (!clickInFrame)
1115
0
    return;
1116
0
1117
0
  SetCaretBidiLevel(clickInFrame->GetEmbeddingLevel());
1118
0
}
1119
1120
1121
bool
1122
nsFrameSelection::AdjustForMaintainedSelection(nsIContent *aContent,
1123
                                               int32_t     aOffset)
1124
0
{
1125
0
  if (!mMaintainRange)
1126
0
    return false;
1127
0
1128
0
  if (!aContent) {
1129
0
    return false;
1130
0
  }
1131
0
1132
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
1133
0
  if (!mDomSelections[index])
1134
0
    return false;
1135
0
1136
0
  nsINode* rangeStartNode = mMaintainRange->GetStartContainer();
1137
0
  nsINode* rangeEndNode = mMaintainRange->GetEndContainer();
1138
0
  int32_t rangeStartOffset = mMaintainRange->StartOffset();
1139
0
  int32_t rangeEndOffset = mMaintainRange->EndOffset();
1140
0
1141
0
  int32_t relToStart =
1142
0
    nsContentUtils::ComparePoints(rangeStartNode, rangeStartOffset,
1143
0
                                  aContent, aOffset);
1144
0
  int32_t relToEnd =
1145
0
    nsContentUtils::ComparePoints(rangeEndNode, rangeEndOffset,
1146
0
                                  aContent, aOffset);
1147
0
1148
0
  // If aContent/aOffset is inside the maintained selection, or if it is on the
1149
0
  // "anchor" side of the maintained selection, we need to do something.
1150
0
  if ((relToStart < 0 && relToEnd > 0) ||
1151
0
      (relToStart > 0 &&
1152
0
       mDomSelections[index]->GetDirection() == eDirNext) ||
1153
0
      (relToEnd < 0 &&
1154
0
       mDomSelections[index]->GetDirection() == eDirPrevious)) {
1155
0
    // Set the current range to the maintained range.
1156
0
    mDomSelections[index]->ReplaceAnchorFocusRange(mMaintainRange);
1157
0
    if (relToStart < 0 && relToEnd > 0) {
1158
0
      // We're inside the maintained selection, just keep it selected.
1159
0
      return true;
1160
0
    }
1161
0
    // Reverse the direction of the selection so that the anchor will be on the
1162
0
    // far side of the maintained selection, relative to aContent/aOffset.
1163
0
    mDomSelections[index]->SetDirection(relToStart > 0 ? eDirPrevious : eDirNext);
1164
0
  }
1165
0
  return false;
1166
0
}
1167
1168
1169
nsresult
1170
nsFrameSelection::HandleClick(nsIContent*        aNewFocus,
1171
                              uint32_t           aContentOffset,
1172
                              uint32_t           aContentEndOffset,
1173
                              bool               aContinueSelection,
1174
                              bool               aMultipleSelection,
1175
                              CaretAssociateHint aHint)
1176
0
{
1177
0
  if (!aNewFocus)
1178
0
    return NS_ERROR_INVALID_ARG;
1179
0
1180
0
  InvalidateDesiredPos();
1181
0
1182
0
  if (!aContinueSelection) {
1183
0
    mMaintainRange = nullptr;
1184
0
    if (!IsValidSelectionPoint(this, aNewFocus)) {
1185
0
      mAncestorLimiter = nullptr;
1186
0
    }
1187
0
  }
1188
0
1189
0
  // Don't take focus when dragging off of a table
1190
0
  if (!mDragSelectingCells)
1191
0
  {
1192
0
    BidiLevelFromClick(aNewFocus, aContentOffset);
1193
0
    PostReason(nsISelectionListener::MOUSEDOWN_REASON + nsISelectionListener::DRAG_REASON);
1194
0
    if (aContinueSelection &&
1195
0
        AdjustForMaintainedSelection(aNewFocus, aContentOffset))
1196
0
      return NS_OK; //shift clicked to maintained selection. rejected.
1197
0
1198
0
    int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
1199
0
    AutoPrepareFocusRange prep(mDomSelections[index], aContinueSelection, aMultipleSelection);
1200
0
    return TakeFocus(aNewFocus, aContentOffset, aContentEndOffset, aHint,
1201
0
                     aContinueSelection, aMultipleSelection);
1202
0
  }
1203
0
1204
0
  return NS_OK;
1205
0
}
1206
1207
void
1208
nsFrameSelection::HandleDrag(nsIFrame* aFrame, const nsPoint& aPoint)
1209
0
{
1210
0
  if (!aFrame || !mShell)
1211
0
    return;
1212
0
1213
0
  nsresult result;
1214
0
  nsIFrame *newFrame = 0;
1215
0
  nsPoint   newPoint;
1216
0
1217
0
  result = ConstrainFrameAndPointToAnchorSubtree(aFrame, aPoint, &newFrame, newPoint);
1218
0
  if (NS_FAILED(result))
1219
0
    return;
1220
0
  if (!newFrame)
1221
0
    return;
1222
0
1223
0
  nsIFrame::ContentOffsets offsets =
1224
0
      newFrame->GetContentOffsetsFromPoint(newPoint);
1225
0
  if (!offsets.content)
1226
0
    return;
1227
0
1228
0
  if (newFrame->IsSelected() &&
1229
0
      AdjustForMaintainedSelection(offsets.content, offsets.offset))
1230
0
    return;
1231
0
1232
0
  // Adjust offsets according to maintained amount
1233
0
  if (mMaintainRange &&
1234
0
      mMaintainedAmount != eSelectNoAmount) {
1235
0
1236
0
    nsINode* rangenode = mMaintainRange->GetStartContainer();
1237
0
    int32_t rangeOffset = mMaintainRange->StartOffset();
1238
0
    int32_t relativePosition =
1239
0
      nsContentUtils::ComparePoints(rangenode, rangeOffset,
1240
0
                                    offsets.content, offsets.offset);
1241
0
1242
0
    nsDirection direction = relativePosition > 0 ? eDirPrevious : eDirNext;
1243
0
    nsSelectionAmount amount = mMaintainedAmount;
1244
0
    if (amount == eSelectBeginLine && direction == eDirNext)
1245
0
      amount = eSelectEndLine;
1246
0
1247
0
    int32_t offset;
1248
0
    nsIFrame* frame = GetFrameForNodeOffset(offsets.content, offsets.offset,
1249
0
        CARET_ASSOCIATE_AFTER, &offset);
1250
0
1251
0
    if (frame && amount == eSelectWord && direction == eDirPrevious) {
1252
0
      // To avoid selecting the previous word when at start of word,
1253
0
      // first move one character forward.
1254
0
      nsPeekOffsetStruct charPos(eSelectCharacter, eDirNext, offset,
1255
0
                                 nsPoint(0, 0), false, mLimiter != nullptr,
1256
0
                                 false, false, false);
1257
0
      if (NS_SUCCEEDED(frame->PeekOffset(&charPos))) {
1258
0
        frame = charPos.mResultFrame;
1259
0
        offset = charPos.mContentOffset;
1260
0
      }
1261
0
    }
1262
0
1263
0
    nsPeekOffsetStruct pos(amount, direction, offset, nsPoint(0, 0),
1264
0
                           false, mLimiter != nullptr, false, false, false);
1265
0
1266
0
    if (frame && NS_SUCCEEDED(frame->PeekOffset(&pos)) && pos.mResultContent) {
1267
0
      offsets.content = pos.mResultContent;
1268
0
      offsets.offset = pos.mContentOffset;
1269
0
    }
1270
0
  }
1271
0
1272
0
  HandleClick(offsets.content, offsets.offset, offsets.offset,
1273
0
              true, false, offsets.associate);
1274
0
}
1275
1276
nsresult
1277
nsFrameSelection::StartAutoScrollTimer(nsIFrame* aFrame,
1278
                                       const nsPoint& aPoint,
1279
                                       uint32_t  aDelay)
1280
0
{
1281
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
1282
0
  if (!mDomSelections[index]) {
1283
0
    return NS_ERROR_NULL_POINTER;
1284
0
  }
1285
0
1286
0
  return mDomSelections[index]->StartAutoScrollTimer(aFrame, aPoint, aDelay);
1287
0
}
1288
1289
void
1290
nsFrameSelection::StopAutoScrollTimer()
1291
0
{
1292
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
1293
0
  if (!mDomSelections[index]) {
1294
0
    return;
1295
0
  }
1296
0
1297
0
  mDomSelections[index]->StopAutoScrollTimer();
1298
0
}
1299
1300
/**
1301
hard to go from nodes to frames, easy the other way!
1302
 */
1303
nsresult
1304
nsFrameSelection::TakeFocus(nsIContent*        aNewFocus,
1305
                            uint32_t           aContentOffset,
1306
                            uint32_t           aContentEndOffset,
1307
                            CaretAssociateHint aHint,
1308
                            bool               aContinueSelection,
1309
                            bool               aMultipleSelection)
1310
0
{
1311
0
  if (!aNewFocus)
1312
0
    return NS_ERROR_NULL_POINTER;
1313
0
1314
0
  NS_ENSURE_STATE(mShell);
1315
0
1316
0
  if (!IsValidSelectionPoint(this,aNewFocus))
1317
0
    return NS_ERROR_FAILURE;
1318
0
1319
0
  // Clear all table selection data
1320
0
  mSelectingTableCellMode = TableSelection::None;
1321
0
  mDragSelectingCells = false;
1322
0
  mStartSelectedCell = nullptr;
1323
0
  mEndSelectedCell = nullptr;
1324
0
  mAppendStartSelectedCell = nullptr;
1325
0
  mHint = aHint;
1326
0
1327
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
1328
0
  if (!mDomSelections[index])
1329
0
    return NS_ERROR_NULL_POINTER;
1330
0
1331
0
  Maybe<Selection::AutoUserInitiated> userSelect;
1332
0
  if (IsUserSelectionReason()) {
1333
0
    userSelect.emplace(mDomSelections[index]);
1334
0
  }
1335
0
1336
0
  //traverse through document and unselect crap here
1337
0
  if (!aContinueSelection) {//single click? setting cursor down
1338
0
    uint32_t batching = mBatching;//hack to use the collapse code.
1339
0
    bool changes = mChangesDuringBatching;
1340
0
    mBatching = 1;
1341
0
1342
0
    if (aMultipleSelection) {
1343
0
      // Remove existing collapsed ranges as there's no point in having
1344
0
      // non-anchor/focus collapsed ranges.
1345
0
      mDomSelections[index]->RemoveCollapsedRanges();
1346
0
1347
0
      RefPtr<nsRange> newRange = new nsRange(aNewFocus);
1348
0
1349
0
      newRange->CollapseTo(aNewFocus, aContentOffset);
1350
0
      mDomSelections[index]->AddRange(*newRange, IgnoreErrors());
1351
0
      mBatching = batching;
1352
0
      mChangesDuringBatching = changes;
1353
0
    } else {
1354
0
      bool oldDesiredPosSet = mDesiredPosSet; //need to keep old desired position if it was set.
1355
0
      mDomSelections[index]->Collapse(aNewFocus, aContentOffset);
1356
0
      mDesiredPosSet = oldDesiredPosSet; //now reset desired pos back.
1357
0
      mBatching = batching;
1358
0
      mChangesDuringBatching = changes;
1359
0
    }
1360
0
    if (aContentEndOffset != aContentOffset) {
1361
0
      mDomSelections[index]->Extend(aNewFocus, aContentEndOffset);
1362
0
    }
1363
0
1364
0
    //find out if we are inside a table. if so, find out which one and which cell
1365
0
    //once we do that, the next time we get a takefocus, check the parent tree.
1366
0
    //if we are no longer inside same table ,cell then switch to table selection mode.
1367
0
    // BUT only do this in an editor
1368
0
1369
0
    NS_ENSURE_STATE(mShell);
1370
0
    bool editableCell = false;
1371
0
    mCellParent = nullptr;
1372
0
    RefPtr<nsPresContext> context = mShell->GetPresContext();
1373
0
    if (context) {
1374
0
      RefPtr<HTMLEditor> htmlEditor = nsContentUtils::GetHTMLEditor(context);
1375
0
      if (htmlEditor) {
1376
0
        nsINode* cellparent = GetCellParent(aNewFocus);
1377
0
        nsCOMPtr<nsINode> editorHostNode = htmlEditor->GetActiveEditingHost();
1378
0
        editableCell = cellparent && editorHostNode &&
1379
0
                   nsContentUtils::ContentIsDescendantOf(cellparent, editorHostNode);
1380
0
        if (editableCell) {
1381
0
          mCellParent = cellparent;
1382
#ifdef DEBUG_TABLE_SELECTION
1383
          printf(" * TakeFocus - Collapsing into new cell\n");
1384
#endif
1385
        }
1386
0
      }
1387
0
    }
1388
0
  }
1389
0
  else {
1390
0
    // Now update the range list:
1391
0
    if (aContinueSelection && aNewFocus)
1392
0
    {
1393
0
      int32_t offset;
1394
0
      nsINode *cellparent = GetCellParent(aNewFocus);
1395
0
      if (mCellParent && cellparent && cellparent != mCellParent) //switch to cell selection mode
1396
0
      {
1397
#ifdef DEBUG_TABLE_SELECTION
1398
printf(" * TakeFocus - moving into new cell\n");
1399
#endif
1400
        WidgetMouseEvent event(false, eVoidEvent, nullptr,
1401
0
                               WidgetMouseEvent::eReal);
1402
0
1403
0
        // Start selecting in the cell we were in before
1404
0
        nsINode* parent = ParentOffset(mCellParent, &offset);
1405
0
        if (parent)
1406
0
          HandleTableSelection(parent, offset, TableSelection::Cell, &event);
1407
0
1408
0
        // Find the parent of this new cell and extend selection to it
1409
0
        parent = ParentOffset(cellparent, &offset);
1410
0
1411
0
        // XXXX We need to REALLY get the current key shift state
1412
0
        //  (we'd need to add event listener -- let's not bother for now)
1413
0
        event.mModifiers &= ~MODIFIER_SHIFT; //aContinueSelection;
1414
0
        if (parent)
1415
0
        {
1416
0
          mCellParent = cellparent;
1417
0
          // Continue selection into next cell
1418
0
          HandleTableSelection(parent, offset, TableSelection::Cell, &event);
1419
0
        }
1420
0
      }
1421
0
      else
1422
0
      {
1423
0
        // XXXX Problem: Shift+click in browser is appending text selection to selected table!!!
1424
0
        //   is this the place to erase seleced cells ?????
1425
0
        if (mDomSelections[index]->GetDirection() == eDirNext && aContentEndOffset > aContentOffset) //didn't go far enough
1426
0
        {
1427
0
          mDomSelections[index]->Extend(aNewFocus, aContentEndOffset);//this will only redraw the diff
1428
0
        }
1429
0
        else
1430
0
          mDomSelections[index]->Extend(aNewFocus, aContentOffset);
1431
0
      }
1432
0
    }
1433
0
  }
1434
0
1435
0
  // Don't notify selection listeners if batching is on:
1436
0
  if (GetBatching())
1437
0
    return NS_OK;
1438
0
1439
0
  // Be aware, the Selection instance may be destroyed after this call.
1440
0
  return NotifySelectionListeners(SelectionType::eNormal);
1441
0
}
1442
1443
1444
UniquePtr<SelectionDetails>
1445
nsFrameSelection::LookUpSelection(nsIContent *aContent,
1446
                                  int32_t aContentOffset,
1447
                                  int32_t aContentLength,
1448
                                  bool aSlowCheck) const
1449
0
{
1450
0
  if (!aContent || !mShell)
1451
0
    return nullptr;
1452
0
1453
0
  UniquePtr<SelectionDetails> details;
1454
0
1455
0
  for (size_t j = 0; j < ArrayLength(mDomSelections); j++) {
1456
0
    if (mDomSelections[j]) {
1457
0
      details = mDomSelections[j]->LookUpSelection(aContent, aContentOffset,
1458
0
                                                   aContentLength, std::move(details),
1459
0
                                                   kPresentSelectionTypes[j],
1460
0
                                                   aSlowCheck);
1461
0
    }
1462
0
  }
1463
0
1464
0
  return details;
1465
0
}
1466
1467
void
1468
nsFrameSelection::SetDragState(bool aState)
1469
0
{
1470
0
  if (mDragState == aState)
1471
0
    return;
1472
0
1473
0
  mDragState = aState;
1474
0
1475
0
  if (!mDragState)
1476
0
  {
1477
0
    mDragSelectingCells = false;
1478
0
    // Notify that reason is mouse up.
1479
0
    PostReason(nsISelectionListener::MOUSEUP_REASON);
1480
0
    // Be aware, the Selection instance may be destroyed after this call.
1481
0
    NotifySelectionListeners(SelectionType::eNormal);
1482
0
  }
1483
0
}
1484
1485
Selection*
1486
nsFrameSelection::GetSelection(SelectionType aSelectionType) const
1487
0
{
1488
0
  int8_t index = GetIndexFromSelectionType(aSelectionType);
1489
0
  if (index < 0)
1490
0
    return nullptr;
1491
0
1492
0
  return mDomSelections[index];
1493
0
}
1494
1495
nsresult
1496
nsFrameSelection::ScrollSelectionIntoView(SelectionType aSelectionType,
1497
                                          SelectionRegion aRegion,
1498
                                          int16_t         aFlags) const
1499
0
{
1500
0
  int8_t index = GetIndexFromSelectionType(aSelectionType);
1501
0
  if (index < 0)
1502
0
    return NS_ERROR_INVALID_ARG;
1503
0
1504
0
  if (!mDomSelections[index])
1505
0
    return NS_ERROR_NULL_POINTER;
1506
0
1507
0
  nsIPresShell::ScrollAxis verticalScroll = nsIPresShell::ScrollAxis();
1508
0
  int32_t flags = Selection::SCROLL_DO_FLUSH;
1509
0
  if (aFlags & nsISelectionController::SCROLL_SYNCHRONOUS) {
1510
0
    flags |= Selection::SCROLL_SYNCHRONOUS;
1511
0
  } else if (aFlags & nsISelectionController::SCROLL_FIRST_ANCESTOR_ONLY) {
1512
0
    flags |= Selection::SCROLL_FIRST_ANCESTOR_ONLY;
1513
0
  }
1514
0
  if (aFlags & nsISelectionController::SCROLL_OVERFLOW_HIDDEN) {
1515
0
    flags |= Selection::SCROLL_OVERFLOW_HIDDEN;
1516
0
  }
1517
0
  if (aFlags & nsISelectionController::SCROLL_CENTER_VERTICALLY) {
1518
0
    verticalScroll = nsIPresShell::ScrollAxis(
1519
0
      nsIPresShell::SCROLL_CENTER, nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE);
1520
0
  }
1521
0
  if (aFlags & nsISelectionController::SCROLL_FOR_CARET_MOVE) {
1522
0
    flags |= Selection::SCROLL_FOR_CARET_MOVE;
1523
0
  }
1524
0
1525
0
  // After ScrollSelectionIntoView(), the pending notifications might be
1526
0
  // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
1527
0
  RefPtr<Selection> sel = mDomSelections[index];
1528
0
  return sel->ScrollIntoView(aRegion, verticalScroll,
1529
0
                             nsIPresShell::ScrollAxis(), flags);
1530
0
}
1531
1532
nsresult
1533
nsFrameSelection::RepaintSelection(SelectionType aSelectionType)
1534
0
{
1535
0
  int8_t index = GetIndexFromSelectionType(aSelectionType);
1536
0
  if (index < 0)
1537
0
    return NS_ERROR_INVALID_ARG;
1538
0
  if (!mDomSelections[index])
1539
0
    return NS_ERROR_NULL_POINTER;
1540
0
  NS_ENSURE_STATE(mShell);
1541
0
1542
0
// On macOS, update the selection cache to the new active selection
1543
0
// aka the current selection.
1544
#ifdef XP_MACOSX
1545
  nsFocusManager* fm = nsFocusManager::GetFocusManager();
1546
  // Check an active window exists otherwise there cannot be a current selection
1547
  // and that it's a normal selection.
1548
  if (fm->GetActiveWindow() && aSelectionType == SelectionType::eNormal) {
1549
    UpdateSelectionCacheOnRepaintSelection(mDomSelections[index]);
1550
  }
1551
#endif
1552
0
  return mDomSelections[index]->Repaint(mShell->GetPresContext());
1553
0
}
1554
1555
static bool
1556
IsDisplayContents(const nsIContent* aContent)
1557
0
{
1558
0
  return aContent->IsElement() && aContent->AsElement()->IsDisplayContents();
1559
0
}
1560
1561
nsIFrame*
1562
nsFrameSelection::GetFrameForNodeOffset(nsIContent*        aNode,
1563
                                        int32_t            aOffset,
1564
                                        CaretAssociateHint aHint,
1565
                                        int32_t*           aReturnOffset) const
1566
0
{
1567
0
  if (!aNode || !aReturnOffset || !mShell)
1568
0
    return nullptr;
1569
0
1570
0
  if (aOffset < 0)
1571
0
    return nullptr;
1572
0
1573
0
  if (!aNode->GetPrimaryFrame() && !IsDisplayContents(aNode)) {
1574
0
    return nullptr;
1575
0
  }
1576
0
1577
0
  nsIFrame* returnFrame = nullptr;
1578
0
  nsCOMPtr<nsIContent> theNode;
1579
0
1580
0
  while (true) {
1581
0
    *aReturnOffset = aOffset;
1582
0
1583
0
    theNode = aNode;
1584
0
1585
0
    if (aNode->IsElement()) {
1586
0
      int32_t childIndex  = 0;
1587
0
      int32_t numChildren = theNode->GetChildCount();
1588
0
1589
0
      if (aHint == CARET_ASSOCIATE_BEFORE) {
1590
0
        if (aOffset > 0) {
1591
0
          childIndex = aOffset - 1;
1592
0
        } else {
1593
0
          childIndex = aOffset;
1594
0
        }
1595
0
      } else {
1596
0
        NS_ASSERTION(aHint == CARET_ASSOCIATE_AFTER, "unknown direction");
1597
0
        if (aOffset >= numChildren) {
1598
0
          if (numChildren > 0) {
1599
0
            childIndex = numChildren - 1;
1600
0
          } else {
1601
0
            childIndex = 0;
1602
0
          }
1603
0
        } else {
1604
0
          childIndex = aOffset;
1605
0
        }
1606
0
      }
1607
0
1608
0
      if (childIndex > 0 || numChildren > 0) {
1609
0
        nsCOMPtr<nsIContent> childNode = theNode->GetChildAt_Deprecated(childIndex);
1610
0
1611
0
        if (!childNode) {
1612
0
          break;
1613
0
        }
1614
0
1615
0
        theNode = childNode;
1616
0
      }
1617
0
1618
0
      // Now that we have the child node, check if it too
1619
0
      // can contain children. If so, descend into child.
1620
0
      if (theNode->IsElement() &&
1621
0
          theNode->GetChildCount() &&
1622
0
          !theNode->HasIndependentSelection()) {
1623
0
        aNode = theNode;
1624
0
        aOffset = aOffset > childIndex ? theNode->GetChildCount() : 0;
1625
0
        continue;
1626
0
      } else {
1627
0
        // Check to see if theNode is a text node. If it is, translate
1628
0
        // aOffset into an offset into the text node.
1629
0
1630
0
        RefPtr<Text> textNode = theNode->GetAsText();
1631
0
        if (textNode) {
1632
0
          if (theNode->GetPrimaryFrame()) {
1633
0
            if (aOffset > childIndex) {
1634
0
              uint32_t textLength = textNode->Length();
1635
0
1636
0
              *aReturnOffset = (int32_t)textLength;
1637
0
            } else {
1638
0
              *aReturnOffset = 0;
1639
0
            }
1640
0
          } else {
1641
0
            int32_t numChildren = aNode->GetChildCount();
1642
0
            int32_t newChildIndex =
1643
0
              aHint == CARET_ASSOCIATE_BEFORE ? childIndex - 1 : childIndex + 1;
1644
0
1645
0
            if (newChildIndex >= 0 && newChildIndex < numChildren) {
1646
0
              nsCOMPtr<nsIContent> newChildNode = aNode->GetChildAt_Deprecated(newChildIndex);
1647
0
              if (!newChildNode) {
1648
0
                return nullptr;
1649
0
              }
1650
0
1651
0
              aNode = newChildNode;
1652
0
              aOffset = aHint == CARET_ASSOCIATE_BEFORE ? aNode->GetChildCount() : 0;
1653
0
              continue;
1654
0
            } else {
1655
0
              // newChildIndex is illegal which means we're at first or last
1656
0
              // child. Just use original node to get the frame.
1657
0
              theNode = aNode;
1658
0
            }
1659
0
          }
1660
0
        }
1661
0
      }
1662
0
    }
1663
0
1664
0
    // If the node is a ShadowRoot, the frame needs to be adjusted,
1665
0
    // because a ShadowRoot does not get a frame. Its children are rendered
1666
0
    // as children of the host.
1667
0
    if (ShadowRoot* shadow = ShadowRoot::FromNode(theNode)) {
1668
0
      theNode = shadow->GetHost();
1669
0
    }
1670
0
1671
0
    returnFrame = theNode->GetPrimaryFrame();
1672
0
    if (!returnFrame) {
1673
0
      if (aHint == CARET_ASSOCIATE_BEFORE) {
1674
0
        if (aOffset > 0) {
1675
0
          --aOffset;
1676
0
          continue;
1677
0
        } else {
1678
0
          break;
1679
0
        }
1680
0
      } else {
1681
0
        int32_t end = theNode->GetChildCount();
1682
0
        if (aOffset < end) {
1683
0
          ++aOffset;
1684
0
          continue;
1685
0
        } else {
1686
0
          break;
1687
0
        }
1688
0
      }
1689
0
    }
1690
0
1691
0
    break;
1692
0
  } // end while
1693
0
1694
0
  if (!returnFrame)
1695
0
    return nullptr;
1696
0
1697
0
  // If we ended up here and were asked to position the caret after a visible
1698
0
  // break, let's return the frame on the next line instead if it exists.
1699
0
  if (aOffset > 0 &&  (uint32_t) aOffset >= aNode->Length() &&
1700
0
      theNode == aNode->GetLastChild()) {
1701
0
    nsIFrame* newFrame;
1702
0
    nsLayoutUtils::IsInvisibleBreak(theNode, &newFrame);
1703
0
    if (newFrame) {
1704
0
      returnFrame = newFrame;
1705
0
      *aReturnOffset = 0;
1706
0
    }
1707
0
  }
1708
0
1709
0
  // find the child frame containing the offset we want
1710
0
  returnFrame->GetChildFrameContainingOffset(*aReturnOffset, aHint == CARET_ASSOCIATE_AFTER,
1711
0
                                             &aOffset, &returnFrame);
1712
0
  return returnFrame;
1713
0
}
1714
1715
void
1716
nsFrameSelection::CommonPageMove(bool aForward,
1717
                                 bool aExtend,
1718
                                 nsIScrollableFrame* aScrollableFrame)
1719
0
{
1720
0
  // expected behavior for PageMove is to scroll AND move the caret
1721
0
  // and remain relative position of the caret in view. see Bug 4302.
1722
0
1723
0
  //get the frame from the scrollable view
1724
0
1725
0
  nsIFrame* scrolledFrame = aScrollableFrame->GetScrolledFrame();
1726
0
  if (!scrolledFrame)
1727
0
    return;
1728
0
1729
0
  // find out where the caret is.
1730
0
  // we should know mDesiredPos value of nsFrameSelection, but I havent seen that behavior in other windows applications yet.
1731
0
  Selection* domSel = GetSelection(SelectionType::eNormal);
1732
0
  if (!domSel) {
1733
0
    return;
1734
0
  }
1735
0
1736
0
  nsRect caretPos;
1737
0
  nsIFrame* caretFrame = nsCaret::GetGeometry(domSel, &caretPos);
1738
0
  if (!caretFrame)
1739
0
    return;
1740
0
1741
0
  //need to adjust caret jump by percentage scroll
1742
0
  nsSize scrollDelta = aScrollableFrame->GetPageScrollAmount();
1743
0
1744
0
  if (aForward)
1745
0
    caretPos.y += scrollDelta.height;
1746
0
  else
1747
0
    caretPos.y -= scrollDelta.height;
1748
0
1749
0
  caretPos += caretFrame->GetOffsetTo(scrolledFrame);
1750
0
1751
0
  // get a content at desired location
1752
0
  nsPoint desiredPoint;
1753
0
  desiredPoint.x = caretPos.x;
1754
0
  desiredPoint.y = caretPos.y + caretPos.height/2;
1755
0
  nsIFrame::ContentOffsets offsets =
1756
0
      scrolledFrame->GetContentOffsetsFromPoint(desiredPoint);
1757
0
1758
0
  if (!offsets.content)
1759
0
    return;
1760
0
1761
0
  // scroll one page
1762
0
  aScrollableFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
1763
0
                             nsIScrollableFrame::PAGES,
1764
0
                             nsIScrollableFrame::SMOOTH);
1765
0
1766
0
  // place the caret
1767
0
  HandleClick(offsets.content, offsets.offset,
1768
0
              offsets.offset, aExtend, false, CARET_ASSOCIATE_AFTER);
1769
0
}
1770
1771
nsresult
1772
nsFrameSelection::PhysicalMove(int16_t aDirection, int16_t aAmount,
1773
                               bool aExtend)
1774
0
{
1775
0
  NS_ENSURE_STATE(mShell);
1776
0
  // Flush out layout, since we need it to be up to date to do caret
1777
0
  // positioning.
1778
0
  mShell->FlushPendingNotifications(FlushType::Layout);
1779
0
1780
0
  if (!mShell) {
1781
0
    return NS_OK;
1782
0
  }
1783
0
1784
0
  // Check that parameters are safe
1785
0
  if (aDirection < 0 || aDirection > 3 || aAmount < 0 || aAmount > 1) {
1786
0
    return NS_ERROR_FAILURE;
1787
0
  }
1788
0
1789
0
  nsPresContext *context = mShell->GetPresContext();
1790
0
  if (!context) {
1791
0
    return NS_ERROR_FAILURE;
1792
0
  }
1793
0
1794
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
1795
0
  RefPtr<Selection> sel = mDomSelections[index];
1796
0
  if (!sel) {
1797
0
    return NS_ERROR_NULL_POINTER;
1798
0
  }
1799
0
1800
0
  // Map the abstract movement amounts (0-1) to direction-specific
1801
0
  // selection units.
1802
0
  static const nsSelectionAmount inlineAmount[] =
1803
0
    { eSelectCluster, eSelectWord };
1804
0
  static const nsSelectionAmount blockPrevAmount[] =
1805
0
    { eSelectLine, eSelectBeginLine };
1806
0
  static const nsSelectionAmount blockNextAmount[] =
1807
0
    { eSelectLine, eSelectEndLine };
1808
0
1809
0
  struct PhysicalToLogicalMapping {
1810
0
    nsDirection direction;
1811
0
    const nsSelectionAmount *amounts;
1812
0
  };
1813
0
  static const PhysicalToLogicalMapping verticalLR[4] = {
1814
0
    { eDirPrevious, blockPrevAmount },  // left
1815
0
    { eDirNext, blockNextAmount },      // right
1816
0
    { eDirPrevious, inlineAmount }, // up
1817
0
    { eDirNext, inlineAmount }      // down
1818
0
  };
1819
0
  static const PhysicalToLogicalMapping verticalRL[4] = {
1820
0
    { eDirNext, blockNextAmount },
1821
0
    { eDirPrevious, blockPrevAmount },
1822
0
    { eDirPrevious, inlineAmount },
1823
0
    { eDirNext, inlineAmount }
1824
0
  };
1825
0
  static const PhysicalToLogicalMapping horizontal[4] = {
1826
0
    { eDirPrevious, inlineAmount },
1827
0
    { eDirNext, inlineAmount },
1828
0
    { eDirPrevious, blockPrevAmount },
1829
0
    { eDirNext, blockNextAmount }
1830
0
  };
1831
0
1832
0
  WritingMode wm;
1833
0
  nsIFrame *frame = nullptr;
1834
0
  int32_t offsetused = 0;
1835
0
  if (NS_SUCCEEDED(sel->GetPrimaryFrameForFocusNode(&frame, &offsetused,
1836
0
                                                    true))) {
1837
0
    if (frame) {
1838
0
      if (!frame->Style()->IsTextCombined()) {
1839
0
        wm = frame->GetWritingMode();
1840
0
      } else {
1841
0
        // Using different direction for horizontal-in-vertical would
1842
0
        // make it hard to navigate via keyboard. Inherit the moving
1843
0
        // direction from its parent.
1844
0
        MOZ_ASSERT(frame->IsTextFrame());
1845
0
        wm = frame->GetParent()->GetWritingMode();
1846
0
        MOZ_ASSERT(wm.IsVertical(), "Text combined "
1847
0
                   "can only appear in vertical text");
1848
0
      }
1849
0
    }
1850
0
  }
1851
0
1852
0
  const PhysicalToLogicalMapping& mapping =
1853
0
    wm.IsVertical()
1854
0
      ? wm.IsVerticalLR() ? verticalLR[aDirection] : verticalRL[aDirection]
1855
0
      : horizontal[aDirection];
1856
0
1857
0
  nsresult rv = MoveCaret(mapping.direction, aExtend, mapping.amounts[aAmount],
1858
0
                          eVisual);
1859
0
  if (NS_FAILED(rv)) {
1860
0
    // If we tried to do a line move, but couldn't move in the given direction,
1861
0
    // then we'll "promote" this to a line-edge move instead.
1862
0
    if (mapping.amounts[aAmount] == eSelectLine) {
1863
0
      rv = MoveCaret(mapping.direction, aExtend, mapping.amounts[aAmount + 1],
1864
0
                     eVisual);
1865
0
    }
1866
0
    // And if it was a next-word move that failed (which can happen when
1867
0
    // eat_space_to_next_word is true, see bug 1153237), then just move forward
1868
0
    // to the line-edge.
1869
0
    else if (mapping.amounts[aAmount] == eSelectWord &&
1870
0
             mapping.direction == eDirNext) {
1871
0
      rv = MoveCaret(eDirNext, aExtend, eSelectEndLine, eVisual);
1872
0
    }
1873
0
  }
1874
0
1875
0
  return rv;
1876
0
}
1877
1878
nsresult
1879
nsFrameSelection::CharacterMove(bool aForward, bool aExtend)
1880
0
{
1881
0
  return MoveCaret(aForward ? eDirNext : eDirPrevious, aExtend, eSelectCluster,
1882
0
                   eUsePrefStyle);
1883
0
}
1884
1885
nsresult
1886
nsFrameSelection::CharacterExtendForDelete()
1887
0
{
1888
0
  return MoveCaret(eDirNext, true, eSelectCluster, eLogical);
1889
0
}
1890
1891
nsresult
1892
nsFrameSelection::CharacterExtendForBackspace()
1893
0
{
1894
0
  return MoveCaret(eDirPrevious, true, eSelectCharacter, eLogical);
1895
0
}
1896
1897
nsresult
1898
nsFrameSelection::WordMove(bool aForward, bool aExtend)
1899
0
{
1900
0
  return MoveCaret(aForward ? eDirNext : eDirPrevious, aExtend, eSelectWord,
1901
0
                   eUsePrefStyle);
1902
0
}
1903
1904
nsresult
1905
nsFrameSelection::WordExtendForDelete(bool aForward)
1906
0
{
1907
0
  return MoveCaret(aForward ? eDirNext : eDirPrevious, true, eSelectWord,
1908
0
                   eLogical);
1909
0
}
1910
1911
nsresult
1912
nsFrameSelection::LineMove(bool aForward, bool aExtend)
1913
0
{
1914
0
  return MoveCaret(aForward ? eDirNext : eDirPrevious, aExtend, eSelectLine,
1915
0
                   eUsePrefStyle);
1916
0
}
1917
1918
nsresult
1919
nsFrameSelection::IntraLineMove(bool aForward, bool aExtend)
1920
0
{
1921
0
  if (aForward) {
1922
0
    return MoveCaret(eDirNext, aExtend, eSelectEndLine, eLogical);
1923
0
  } else {
1924
0
    return MoveCaret(eDirPrevious, aExtend, eSelectBeginLine, eLogical);
1925
0
  }
1926
0
}
1927
1928
nsresult
1929
nsFrameSelection::SelectAll()
1930
0
{
1931
0
  nsCOMPtr<nsIContent> rootContent;
1932
0
  if (mLimiter)
1933
0
  {
1934
0
    rootContent = mLimiter;//addrefit
1935
0
  }
1936
0
  else if (mAncestorLimiter) {
1937
0
    rootContent = mAncestorLimiter;
1938
0
  }
1939
0
  else
1940
0
  {
1941
0
    NS_ENSURE_STATE(mShell);
1942
0
    nsIDocument *doc = mShell->GetDocument();
1943
0
    if (!doc)
1944
0
      return NS_ERROR_FAILURE;
1945
0
    rootContent = doc->GetRootElement();
1946
0
    if (!rootContent)
1947
0
      return NS_ERROR_FAILURE;
1948
0
  }
1949
0
  int32_t numChildren = rootContent->GetChildCount();
1950
0
  PostReason(nsISelectionListener::NO_REASON);
1951
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
1952
0
  AutoPrepareFocusRange prep(mDomSelections[index], false, false);
1953
0
  return TakeFocus(rootContent, 0, numChildren, CARET_ASSOCIATE_BEFORE, false, false);
1954
0
}
1955
1956
//////////END FRAMESELECTION
1957
1958
void
1959
nsFrameSelection::StartBatchChanges()
1960
0
{
1961
0
  mBatching++;
1962
0
}
1963
1964
void
1965
nsFrameSelection::EndBatchChanges(int16_t aReason)
1966
0
{
1967
0
  mBatching--;
1968
0
  NS_ASSERTION(mBatching >=0,"Bad mBatching");
1969
0
1970
0
  if (mBatching == 0 && mChangesDuringBatching) {
1971
0
    int16_t postReason = PopReason() | aReason;
1972
0
    PostReason(postReason);
1973
0
    mChangesDuringBatching = false;
1974
0
    // Be aware, the Selection instance may be destroyed after this call.
1975
0
    NotifySelectionListeners(SelectionType::eNormal);
1976
0
  }
1977
0
}
1978
1979
1980
nsresult
1981
nsFrameSelection::NotifySelectionListeners(SelectionType aSelectionType)
1982
0
{
1983
0
  int8_t index = GetIndexFromSelectionType(aSelectionType);
1984
0
  if (index >=0 && mDomSelections[index])
1985
0
  {
1986
0
    RefPtr<Selection> selection = mDomSelections[index];
1987
0
    return selection->NotifySelectionListeners();
1988
0
  }
1989
0
  return NS_ERROR_FAILURE;
1990
0
}
1991
1992
// Start of Table Selection methods
1993
1994
static bool IsCell(nsIContent *aContent)
1995
0
{
1996
0
  return aContent->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th);
1997
0
}
1998
1999
nsITableCellLayout*
2000
nsFrameSelection::GetCellLayout(nsIContent *aCellContent) const
2001
0
{
2002
0
  NS_ENSURE_TRUE(mShell, nullptr);
2003
0
  nsITableCellLayout *cellLayoutObject =
2004
0
    do_QueryFrame(aCellContent->GetPrimaryFrame());
2005
0
  return cellLayoutObject;
2006
0
}
2007
2008
nsresult
2009
nsFrameSelection::ClearNormalSelection()
2010
0
{
2011
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
2012
0
  if (!mDomSelections[index])
2013
0
    return NS_ERROR_NULL_POINTER;
2014
0
2015
0
  ErrorResult err;
2016
0
  mDomSelections[index]->RemoveAllRanges(err);
2017
0
  return err.StealNSResult();
2018
0
}
2019
2020
static nsIContent*
2021
GetFirstSelectedContent(nsRange* aRange)
2022
0
{
2023
0
  if (!aRange) {
2024
0
    return nullptr;
2025
0
  }
2026
0
2027
0
  MOZ_ASSERT(aRange->GetStartContainer(), "Must have start parent!");
2028
0
  MOZ_ASSERT(aRange->GetStartContainer()->IsElement(), "Unexpected parent");
2029
0
2030
0
  return aRange->GetChildAtStartOffset();
2031
0
}
2032
2033
// Table selection support.
2034
// TODO: Separate table methods into a separate nsITableSelection interface
2035
nsresult
2036
nsFrameSelection::HandleTableSelection(nsINode* aParentContent,
2037
                                       int32_t aContentOffset,
2038
                                       TableSelection aTarget,
2039
                                       WidgetMouseEvent* aMouseEvent)
2040
0
{
2041
0
  NS_ENSURE_TRUE(aParentContent, NS_ERROR_NULL_POINTER);
2042
0
  NS_ENSURE_TRUE(aMouseEvent, NS_ERROR_NULL_POINTER);
2043
0
2044
0
  if (mDragState && mDragSelectingCells && aTarget == TableSelection::Table)
2045
0
  {
2046
0
    // We were selecting cells and user drags mouse in table border or inbetween cells,
2047
0
    //  just do nothing
2048
0
      return NS_OK;
2049
0
  }
2050
0
2051
0
  nsresult result = NS_OK;
2052
0
2053
0
  nsIContent *childContent = aParentContent->GetChildAt_Deprecated(aContentOffset);
2054
0
2055
0
  // When doing table selection, always set the direction to next so
2056
0
  // we can be sure that anchorNode's offset always points to the
2057
0
  // selected cell
2058
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
2059
0
  if (!mDomSelections[index])
2060
0
    return NS_ERROR_NULL_POINTER;
2061
0
2062
0
  mDomSelections[index]->SetDirection(eDirNext);
2063
0
2064
0
  // Stack-class to wrap all table selection changes in
2065
0
  //  BeginBatchChanges() / EndBatchChanges()
2066
0
  SelectionBatcher selectionBatcher(mDomSelections[index]);
2067
0
2068
0
  int32_t startRowIndex, startColIndex, curRowIndex, curColIndex;
2069
0
  if (mDragState && mDragSelectingCells)
2070
0
  {
2071
0
    // We are drag-selecting
2072
0
    if (aTarget != TableSelection::Table)
2073
0
    {
2074
0
      // If dragging in the same cell as last event, do nothing
2075
0
      if (mEndSelectedCell == childContent)
2076
0
        return NS_OK;
2077
0
2078
#ifdef DEBUG_TABLE_SELECTION
2079
      printf(" mStartSelectedCell = %p, mEndSelectedCell = %p, childContent = %p \n",
2080
             mStartSelectedCell.get(), mEndSelectedCell.get(), childContent);
2081
#endif
2082
      // aTarget can be any "cell mode",
2083
0
      //  so we can easily drag-select rows and columns
2084
0
      // Once we are in row or column mode,
2085
0
      //  we can drift into any cell to stay in that mode
2086
0
      //  even if aTarget = TableSelection::Cell
2087
0
2088
0
      if (mSelectingTableCellMode == TableSelection::Row ||
2089
0
          mSelectingTableCellMode == TableSelection::Column)
2090
0
      {
2091
0
        if (mEndSelectedCell)
2092
0
        {
2093
0
          // Also check if cell is in same row/col
2094
0
          result = GetCellIndexes(mEndSelectedCell, startRowIndex, startColIndex);
2095
0
          if (NS_FAILED(result)) return result;
2096
0
          result = GetCellIndexes(childContent, curRowIndex, curColIndex);
2097
0
          if (NS_FAILED(result)) return result;
2098
0
2099
#ifdef DEBUG_TABLE_SELECTION
2100
printf(" curRowIndex = %d, startRowIndex = %d, curColIndex = %d, startColIndex = %d\n", curRowIndex, startRowIndex, curColIndex, startColIndex);
2101
#endif
2102
0
          if ((mSelectingTableCellMode == TableSelection::Row && startRowIndex == curRowIndex) ||
2103
0
              (mSelectingTableCellMode == TableSelection::Column && startColIndex == curColIndex))
2104
0
            return NS_OK;
2105
0
        }
2106
#ifdef DEBUG_TABLE_SELECTION
2107
printf(" Dragged into a new column or row\n");
2108
#endif
2109
        // Continue dragging row or column selection
2110
0
        return SelectRowOrColumn(childContent, mSelectingTableCellMode);
2111
0
      }
2112
0
      else if (mSelectingTableCellMode == TableSelection::Cell)
2113
0
      {
2114
#ifdef DEBUG_TABLE_SELECTION
2115
printf("HandleTableSelection: Dragged into a new cell\n");
2116
#endif
2117
        // Trick for quick selection of rows and columns
2118
0
        // Hold down shift, then start selecting in one direction
2119
0
        // If next cell dragged into is in same row, select entire row,
2120
0
        //   if next cell is in same column, select entire column
2121
0
        if (mStartSelectedCell && aMouseEvent->IsShift())
2122
0
        {
2123
0
          result = GetCellIndexes(mStartSelectedCell, startRowIndex, startColIndex);
2124
0
          if (NS_FAILED(result)) return result;
2125
0
          result = GetCellIndexes(childContent, curRowIndex, curColIndex);
2126
0
          if (NS_FAILED(result)) return result;
2127
0
2128
0
          if (startRowIndex == curRowIndex ||
2129
0
              startColIndex == curColIndex)
2130
0
          {
2131
0
            // Force new selection block
2132
0
            mStartSelectedCell = nullptr;
2133
0
            mDomSelections[index]->RemoveAllRanges(IgnoreErrors());
2134
0
2135
0
            if (startRowIndex == curRowIndex)
2136
0
              mSelectingTableCellMode = TableSelection::Row;
2137
0
            else
2138
0
              mSelectingTableCellMode = TableSelection::Column;
2139
0
2140
0
            return SelectRowOrColumn(childContent, mSelectingTableCellMode);
2141
0
          }
2142
0
        }
2143
0
2144
0
        // Reselect block of cells to new end location
2145
0
        return SelectBlockOfCells(mStartSelectedCell, childContent);
2146
0
      }
2147
0
    }
2148
0
    // Do nothing if dragging in table, but outside a cell
2149
0
    return NS_OK;
2150
0
  }
2151
0
  else
2152
0
  {
2153
0
    // Not dragging  -- mouse event is down or up
2154
0
    if (mDragState)
2155
0
    {
2156
#ifdef DEBUG_TABLE_SELECTION
2157
printf("HandleTableSelection: Mouse down event\n");
2158
#endif
2159
      // Clear cell we stored in mouse-down
2160
0
      mUnselectCellOnMouseUp = nullptr;
2161
0
2162
0
      if (aTarget == TableSelection::Cell)
2163
0
      {
2164
0
        bool isSelected = false;
2165
0
2166
0
        // Check if we have other selected cells
2167
0
        nsIContent* previousCellNode =
2168
0
          GetFirstSelectedContent(GetFirstCellRange());
2169
0
        if (previousCellNode)
2170
0
        {
2171
0
          // We have at least 1 other selected cell
2172
0
2173
0
          // Check if new cell is already selected
2174
0
          nsIFrame  *cellFrame = childContent->GetPrimaryFrame();
2175
0
          if (!cellFrame) return NS_ERROR_NULL_POINTER;
2176
0
          isSelected = cellFrame->IsSelected();
2177
0
        }
2178
0
        else
2179
0
        {
2180
0
          // No cells selected -- remove non-cell selection
2181
0
          mDomSelections[index]->RemoveAllRanges(IgnoreErrors());
2182
0
        }
2183
0
        mDragSelectingCells = true;    // Signal to start drag-cell-selection
2184
0
        mSelectingTableCellMode = aTarget;
2185
0
        // Set start for new drag-selection block (not appended)
2186
0
        mStartSelectedCell = childContent;
2187
0
        // The initial block end is same as the start
2188
0
        mEndSelectedCell = childContent;
2189
0
2190
0
        if (isSelected)
2191
0
        {
2192
0
          // Remember this cell to (possibly) unselect it on mouseup
2193
0
          mUnselectCellOnMouseUp = childContent;
2194
#ifdef DEBUG_TABLE_SELECTION
2195
printf("HandleTableSelection: Saving mUnselectCellOnMouseUp\n");
2196
#endif
2197
        }
2198
0
        else
2199
0
        {
2200
0
          // Select an unselected cell
2201
0
          // but first remove existing selection if not in same table
2202
0
          if (previousCellNode &&
2203
0
              !IsInSameTable(previousCellNode, childContent))
2204
0
          {
2205
0
            mDomSelections[index]->RemoveAllRanges(IgnoreErrors());
2206
0
            // Reset selection mode that is cleared in RemoveAllRanges
2207
0
            mSelectingTableCellMode = aTarget;
2208
0
          }
2209
0
2210
0
          return SelectCellElement(childContent);
2211
0
        }
2212
0
2213
0
        return NS_OK;
2214
0
      }
2215
0
      else if (aTarget == TableSelection::Table)
2216
0
      {
2217
0
        //TODO: We currently select entire table when clicked between cells,
2218
0
        //  should we restrict to only around border?
2219
0
        //  *** How do we get location data for cell and click?
2220
0
        mDragSelectingCells = false;
2221
0
        mStartSelectedCell = nullptr;
2222
0
        mEndSelectedCell = nullptr;
2223
0
2224
0
        // Remove existing selection and select the table
2225
0
        mDomSelections[index]->RemoveAllRanges(IgnoreErrors());
2226
0
        return CreateAndAddRange(aParentContent, aContentOffset);
2227
0
      }
2228
0
      else if (aTarget == TableSelection::Row || aTarget == TableSelection::Column)
2229
0
      {
2230
#ifdef DEBUG_TABLE_SELECTION
2231
printf("aTarget == %d\n", aTarget);
2232
#endif
2233
2234
0
        // Start drag-selecting mode so multiple rows/cols can be selected
2235
0
        // Note: Currently, nsFrame::GetDataForTableSelection
2236
0
        //       will never call us for row or column selection on mouse down
2237
0
        mDragSelectingCells = true;
2238
0
2239
0
        // Force new selection block
2240
0
        mStartSelectedCell = nullptr;
2241
0
        mDomSelections[index]->RemoveAllRanges(IgnoreErrors());
2242
0
        // Always do this AFTER RemoveAllRanges
2243
0
        mSelectingTableCellMode = aTarget;
2244
0
        return SelectRowOrColumn(childContent, aTarget);
2245
0
      }
2246
0
    }
2247
0
    else
2248
0
    {
2249
#ifdef DEBUG_TABLE_SELECTION
2250
      printf("HandleTableSelection: Mouse UP event. mDragSelectingCells=%d, mStartSelectedCell=%p\n",
2251
             mDragSelectingCells, mStartSelectedCell.get());
2252
#endif
2253
      // First check if we are extending a block selection
2254
0
      uint32_t rangeCount = mDomSelections[index]->RangeCount();
2255
0
2256
0
      if (rangeCount > 0 && aMouseEvent->IsShift() &&
2257
0
          mAppendStartSelectedCell && mAppendStartSelectedCell != childContent)
2258
0
      {
2259
0
        // Shift key is down: append a block selection
2260
0
        mDragSelectingCells = false;
2261
0
        return SelectBlockOfCells(mAppendStartSelectedCell, childContent);
2262
0
      }
2263
0
2264
0
      if (mDragSelectingCells)
2265
0
        mAppendStartSelectedCell = mStartSelectedCell;
2266
0
2267
0
      mDragSelectingCells = false;
2268
0
      mStartSelectedCell = nullptr;
2269
0
      mEndSelectedCell = nullptr;
2270
0
2271
0
      // Any other mouseup actions require that Ctrl or Cmd key is pressed
2272
0
      //  else stop table selection mode
2273
0
      bool doMouseUpAction = false;
2274
#ifdef XP_MACOSX
2275
      doMouseUpAction = aMouseEvent->IsMeta();
2276
#else
2277
      doMouseUpAction = aMouseEvent->IsControl();
2278
0
#endif
2279
0
      if (!doMouseUpAction)
2280
0
      {
2281
#ifdef DEBUG_TABLE_SELECTION
2282
        printf("HandleTableSelection: Ending cell selection on mouseup: mAppendStartSelectedCell=%p\n",
2283
               mAppendStartSelectedCell.get());
2284
#endif
2285
        return NS_OK;
2286
0
      }
2287
0
      // Unselect a cell only if it wasn't
2288
0
      //  just selected on mousedown
2289
0
      if( childContent == mUnselectCellOnMouseUp)
2290
0
      {
2291
0
        // Scan ranges to find the cell to unselect (the selection range to remove)
2292
0
        // XXXbz it's really weird that this lives outside the loop, so once we
2293
0
        // find one we keep looking at it even if we find no more cells...
2294
0
        nsINode* previousCellParent = nullptr;
2295
#ifdef DEBUG_TABLE_SELECTION
2296
printf("HandleTableSelection: Unselecting mUnselectCellOnMouseUp; rangeCount=%d\n", rangeCount);
2297
#endif
2298
0
        for (uint32_t i = 0; i < rangeCount; i++)
2299
0
        {
2300
0
          // Strong reference, because sometimes we want to remove
2301
0
          // this range, and then we might be the only owner.
2302
0
          RefPtr<nsRange> range = mDomSelections[index]->GetRangeAt(i);
2303
0
          if (!range) return NS_ERROR_NULL_POINTER;
2304
0
2305
0
          nsINode* container = range->GetStartContainer();
2306
0
          if (!container) {
2307
0
            return NS_ERROR_NULL_POINTER;
2308
0
          }
2309
0
2310
0
          int32_t offset = range->StartOffset();
2311
0
          // Be sure previous selection is a table cell
2312
0
          nsIContent* child = range->GetChildAtStartOffset();
2313
0
          if (child && IsCell(child)) {
2314
0
            previousCellParent = container;
2315
0
          }
2316
0
2317
0
          // We're done if we didn't find parent of a previously-selected cell
2318
0
          if (!previousCellParent) break;
2319
0
2320
0
          if (previousCellParent == aParentContent && offset == aContentOffset)
2321
0
          {
2322
0
            // Cell is already selected
2323
0
            if (rangeCount == 1)
2324
0
            {
2325
#ifdef DEBUG_TABLE_SELECTION
2326
printf("HandleTableSelection: Unselecting single selected cell\n");
2327
#endif
2328
              // This was the only cell selected.
2329
0
              // Collapse to "normal" selection inside the cell
2330
0
              mStartSelectedCell = nullptr;
2331
0
              mEndSelectedCell = nullptr;
2332
0
              mAppendStartSelectedCell = nullptr;
2333
0
              //TODO: We need a "Collapse to just before deepest child" routine
2334
0
              // Even better, should we collapse to just after the LAST deepest child
2335
0
              //  (i.e., at the end of the cell's contents)?
2336
0
              return mDomSelections[index]->Collapse(childContent, 0);
2337
0
            }
2338
#ifdef DEBUG_TABLE_SELECTION
2339
printf("HandleTableSelection: Removing cell from multi-cell selection\n");
2340
#endif
2341
            // Unselecting the start of previous block
2342
0
            // XXX What do we use now!
2343
0
            if (childContent == mAppendStartSelectedCell)
2344
0
               mAppendStartSelectedCell = nullptr;
2345
0
2346
0
            // Deselect cell by removing its range from selection
2347
0
            ErrorResult err;
2348
0
            mDomSelections[index]->RemoveRange(*range, err);
2349
0
            return err.StealNSResult();
2350
0
          }
2351
0
        }
2352
0
        mUnselectCellOnMouseUp = nullptr;
2353
0
      }
2354
0
    }
2355
0
  }
2356
0
  return result;
2357
0
}
2358
2359
nsresult
2360
nsFrameSelection::SelectBlockOfCells(nsIContent *aStartCell, nsIContent *aEndCell)
2361
0
{
2362
0
  NS_ENSURE_TRUE(aStartCell, NS_ERROR_NULL_POINTER);
2363
0
  NS_ENSURE_TRUE(aEndCell, NS_ERROR_NULL_POINTER);
2364
0
  mEndSelectedCell = aEndCell;
2365
0
2366
0
  nsresult result = NS_OK;
2367
0
2368
0
  // If new end cell is in a different table, do nothing
2369
0
  nsIContent* table = IsInSameTable(aStartCell, aEndCell);
2370
0
  if (!table) {
2371
0
    return NS_OK;
2372
0
  }
2373
0
2374
0
  // Get starting and ending cells' location in the cellmap
2375
0
  int32_t startRowIndex, startColIndex, endRowIndex, endColIndex;
2376
0
  result = GetCellIndexes(aStartCell, startRowIndex, startColIndex);
2377
0
  if(NS_FAILED(result)) return result;
2378
0
  result = GetCellIndexes(aEndCell, endRowIndex, endColIndex);
2379
0
  if(NS_FAILED(result)) return result;
2380
0
2381
0
  if (mDragSelectingCells)
2382
0
  {
2383
0
    // Drag selecting: remove selected cells outside of new block limits
2384
0
    UnselectCells(table, startRowIndex, startColIndex, endRowIndex, endColIndex,
2385
0
                  true);
2386
0
  }
2387
0
2388
0
  // Note that we select block in the direction of user's mouse dragging,
2389
0
  //  which means start cell may be after the end cell in either row or column
2390
0
  return AddCellsToSelection(table, startRowIndex, startColIndex,
2391
0
                             endRowIndex, endColIndex);
2392
0
}
2393
2394
nsresult
2395
nsFrameSelection::UnselectCells(nsIContent *aTableContent,
2396
                                int32_t aStartRowIndex,
2397
                                int32_t aStartColumnIndex,
2398
                                int32_t aEndRowIndex,
2399
                                int32_t aEndColumnIndex,
2400
                                bool aRemoveOutsideOfCellRange)
2401
0
{
2402
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
2403
0
  if (!mDomSelections[index])
2404
0
    return NS_ERROR_NULL_POINTER;
2405
0
2406
0
  nsTableWrapperFrame* tableFrame = do_QueryFrame(aTableContent->GetPrimaryFrame());
2407
0
  if (!tableFrame)
2408
0
    return NS_ERROR_FAILURE;
2409
0
2410
0
  int32_t minRowIndex = std::min(aStartRowIndex, aEndRowIndex);
2411
0
  int32_t maxRowIndex = std::max(aStartRowIndex, aEndRowIndex);
2412
0
  int32_t minColIndex = std::min(aStartColumnIndex, aEndColumnIndex);
2413
0
  int32_t maxColIndex = std::max(aStartColumnIndex, aEndColumnIndex);
2414
0
2415
0
  // Strong reference because we sometimes remove the range
2416
0
  RefPtr<nsRange> range = GetFirstCellRange();
2417
0
  nsIContent* cellNode = GetFirstSelectedContent(range);
2418
0
  MOZ_ASSERT(!range || cellNode, "Must have cellNode if had a range");
2419
0
2420
0
  int32_t curRowIndex, curColIndex;
2421
0
  while (cellNode)
2422
0
  {
2423
0
    nsresult result = GetCellIndexes(cellNode, curRowIndex, curColIndex);
2424
0
    if (NS_FAILED(result))
2425
0
      return result;
2426
0
2427
#ifdef DEBUG_TABLE_SELECTION
2428
    if (!range)
2429
      printf("RemoveCellsToSelection -- range is null\n");
2430
#endif
2431
2432
0
    if (range) {
2433
0
      if (aRemoveOutsideOfCellRange) {
2434
0
        if (curRowIndex < minRowIndex || curRowIndex > maxRowIndex ||
2435
0
            curColIndex < minColIndex || curColIndex > maxColIndex) {
2436
0
2437
0
          mDomSelections[index]->RemoveRange(*range, IgnoreErrors());
2438
0
          // Since we've removed the range, decrement pointer to next range
2439
0
          mSelectedCellIndex--;
2440
0
        }
2441
0
2442
0
      } else {
2443
0
        // Remove cell from selection if it belongs to the given cells range or
2444
0
        // it is spanned onto the cells range.
2445
0
        nsTableCellFrame* cellFrame =
2446
0
          tableFrame->GetCellFrameAt(curRowIndex, curColIndex);
2447
0
2448
0
        uint32_t origRowIndex = cellFrame->RowIndex();
2449
0
        uint32_t origColIndex = cellFrame->ColIndex();
2450
0
        uint32_t actualRowSpan =
2451
0
          tableFrame->GetEffectiveRowSpanAt(origRowIndex, origColIndex);
2452
0
        uint32_t actualColSpan =
2453
0
          tableFrame->GetEffectiveColSpanAt(curRowIndex, curColIndex);
2454
0
        if (origRowIndex <= static_cast<uint32_t>(maxRowIndex) && maxRowIndex >= 0 &&
2455
0
            origRowIndex + actualRowSpan - 1 >= static_cast<uint32_t>(minRowIndex) &&
2456
0
            origColIndex <= static_cast<uint32_t>(maxColIndex) && maxColIndex >= 0 &&
2457
0
            origColIndex + actualColSpan - 1 >= static_cast<uint32_t>(minColIndex)) {
2458
0
2459
0
          mDomSelections[index]->RemoveRange(*range, IgnoreErrors());
2460
0
          // Since we've removed the range, decrement pointer to next range
2461
0
          mSelectedCellIndex--;
2462
0
        }
2463
0
      }
2464
0
    }
2465
0
2466
0
    range = GetNextCellRange();
2467
0
    cellNode = GetFirstSelectedContent(range);
2468
0
    MOZ_ASSERT(!range || cellNode, "Must have cellNode if had a range");
2469
0
  }
2470
0
2471
0
  return NS_OK;
2472
0
}
2473
2474
nsresult
2475
nsFrameSelection::AddCellsToSelection(nsIContent *aTableContent,
2476
                                      int32_t aStartRowIndex,
2477
                                      int32_t aStartColumnIndex,
2478
                                      int32_t aEndRowIndex,
2479
                                      int32_t aEndColumnIndex)
2480
0
{
2481
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
2482
0
  if (!mDomSelections[index])
2483
0
    return NS_ERROR_NULL_POINTER;
2484
0
2485
0
  nsTableWrapperFrame* tableFrame = do_QueryFrame(aTableContent->GetPrimaryFrame());
2486
0
  if (!tableFrame) // Check that |table| is a table.
2487
0
    return NS_ERROR_FAILURE;
2488
0
2489
0
  nsresult result = NS_OK;
2490
0
  uint32_t row = aStartRowIndex;
2491
0
  while(true)
2492
0
  {
2493
0
    uint32_t col = aStartColumnIndex;
2494
0
    while(true)
2495
0
    {
2496
0
      nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(row, col);
2497
0
2498
0
      // Skip cells that are spanned from previous locations or are already selected
2499
0
      if (cellFrame) {
2500
0
        uint32_t origRow = cellFrame->RowIndex();
2501
0
        uint32_t origCol = cellFrame->ColIndex();
2502
0
        if (origRow == row && origCol == col && !cellFrame->IsSelected()) {
2503
0
          result = SelectCellElement(cellFrame->GetContent());
2504
0
          if (NS_FAILED(result)) return result;
2505
0
        }
2506
0
      }
2507
0
      // Done when we reach end column
2508
0
      if (col == static_cast<uint32_t>(aEndColumnIndex)) break;
2509
0
2510
0
      if (aStartColumnIndex < aEndColumnIndex)
2511
0
        col ++;
2512
0
      else
2513
0
        col--;
2514
0
    }
2515
0
    if (row == static_cast<uint32_t>(aEndRowIndex)) break;
2516
0
2517
0
    if (aStartRowIndex < aEndRowIndex)
2518
0
      row++;
2519
0
    else
2520
0
      row--;
2521
0
  }
2522
0
  return result;
2523
0
}
2524
2525
nsresult
2526
nsFrameSelection::RemoveCellsFromSelection(nsIContent *aTable,
2527
                                           int32_t aStartRowIndex,
2528
                                           int32_t aStartColumnIndex,
2529
                                           int32_t aEndRowIndex,
2530
                                           int32_t aEndColumnIndex)
2531
0
{
2532
0
  return UnselectCells(aTable, aStartRowIndex, aStartColumnIndex,
2533
0
                       aEndRowIndex, aEndColumnIndex, false);
2534
0
}
2535
2536
nsresult
2537
nsFrameSelection::RestrictCellsToSelection(nsIContent *aTable,
2538
                                           int32_t aStartRowIndex,
2539
                                           int32_t aStartColumnIndex,
2540
                                           int32_t aEndRowIndex,
2541
                                           int32_t aEndColumnIndex)
2542
0
{
2543
0
  return UnselectCells(aTable, aStartRowIndex, aStartColumnIndex,
2544
0
                       aEndRowIndex, aEndColumnIndex, true);
2545
0
}
2546
2547
nsresult
2548
nsFrameSelection::SelectRowOrColumn(nsIContent *aCellContent, TableSelection aTarget)
2549
0
{
2550
0
  if (!aCellContent) return NS_ERROR_NULL_POINTER;
2551
0
2552
0
  nsIContent* table = GetParentTable(aCellContent);
2553
0
  if (!table) return NS_ERROR_NULL_POINTER;
2554
0
2555
0
  // Get table and cell layout interfaces to access
2556
0
  // cell data based on cellmap location
2557
0
  // Frames are not ref counted, so don't use an nsCOMPtr
2558
0
  nsTableWrapperFrame* tableFrame = do_QueryFrame(table->GetPrimaryFrame());
2559
0
  if (!tableFrame) return NS_ERROR_FAILURE;
2560
0
  nsITableCellLayout *cellLayout = GetCellLayout(aCellContent);
2561
0
  if (!cellLayout) return NS_ERROR_FAILURE;
2562
0
2563
0
  // Get location of target cell:
2564
0
  int32_t rowIndex, colIndex;
2565
0
  nsresult result = cellLayout->GetCellIndexes(rowIndex, colIndex);
2566
0
  if (NS_FAILED(result)) return result;
2567
0
2568
0
  // Be sure we start at proper beginning
2569
0
  // (This allows us to select row or col given ANY cell!)
2570
0
  if (aTarget == TableSelection::Row)
2571
0
    colIndex = 0;
2572
0
  if (aTarget == TableSelection::Column)
2573
0
    rowIndex = 0;
2574
0
2575
0
  nsCOMPtr<nsIContent> firstCell, lastCell;
2576
0
  while (true) {
2577
0
    // Loop through all cells in column or row to find first and last
2578
0
    nsCOMPtr<nsIContent> curCellContent =
2579
0
      tableFrame->GetCellAt(rowIndex, colIndex);
2580
0
    if (!curCellContent)
2581
0
      break;
2582
0
2583
0
    if (!firstCell)
2584
0
      firstCell = curCellContent;
2585
0
2586
0
    lastCell = curCellContent.forget();
2587
0
2588
0
    // Move to next cell in cellmap, skipping spanned locations
2589
0
    if (aTarget == TableSelection::Row)
2590
0
      colIndex += tableFrame->GetEffectiveRowSpanAt(rowIndex, colIndex);
2591
0
    else
2592
0
      rowIndex += tableFrame->GetEffectiveRowSpanAt(rowIndex, colIndex);
2593
0
  }
2594
0
2595
0
  // Use SelectBlockOfCells:
2596
0
  // This will replace existing selection,
2597
0
  //  but allow unselecting by dragging out of selected region
2598
0
  if (firstCell && lastCell)
2599
0
  {
2600
0
    if (!mStartSelectedCell)
2601
0
    {
2602
0
      // We are starting a new block, so select the first cell
2603
0
      result = SelectCellElement(firstCell);
2604
0
      if (NS_FAILED(result)) return result;
2605
0
      mStartSelectedCell = firstCell;
2606
0
    }
2607
0
    result = SelectBlockOfCells(mStartSelectedCell, lastCell);
2608
0
2609
0
    // This gets set to the cell at end of row/col,
2610
0
    //   but we need it to be the cell under cursor
2611
0
    mEndSelectedCell = aCellContent;
2612
0
    return result;
2613
0
  }
2614
0
2615
#if 0
2616
// This is a more efficient strategy that appends row to current selection,
2617
//  but doesn't allow dragging OFF of an existing selection to unselect!
2618
  do {
2619
    // Loop through all cells in column or row
2620
    result = tableLayout->GetCellDataAt(rowIndex, colIndex,
2621
                                        getter_AddRefs(cellElement),
2622
                                        curRowIndex, curColIndex,
2623
                                        rowSpan, colSpan,
2624
                                        actualRowSpan, actualColSpan,
2625
                                        isSelected);
2626
    if (NS_FAILED(result)) return result;
2627
    // We're done when cell is not found
2628
    if (!cellElement) break;
2629
2630
2631
    // Check spans else we infinitely loop
2632
    NS_ASSERTION(actualColSpan, "actualColSpan is 0!");
2633
    NS_ASSERTION(actualRowSpan, "actualRowSpan is 0!");
2634
2635
    // Skip cells that are already selected or span from outside our region
2636
    if (!isSelected && rowIndex == curRowIndex && colIndex == curColIndex)
2637
    {
2638
      result = SelectCellElement(cellElement);
2639
      if (NS_FAILED(result)) return result;
2640
    }
2641
    // Move to next row or column in cellmap, skipping spanned locations
2642
    if (aTarget == TableSelection::Row)
2643
      colIndex += actualColSpan;
2644
    else
2645
      rowIndex += actualRowSpan;
2646
  }
2647
  while (cellElement);
2648
#endif
2649
2650
0
  return NS_OK;
2651
0
}
2652
2653
nsIContent*
2654
nsFrameSelection::GetFirstCellNodeInRange(nsRange *aRange) const
2655
0
{
2656
0
  if (!aRange) return nullptr;
2657
0
2658
0
  nsIContent* childContent = aRange->GetChildAtStartOffset();
2659
0
  if (!childContent)
2660
0
    return nullptr;
2661
0
  // Don't return node if not a cell
2662
0
  if (!IsCell(childContent))
2663
0
    return nullptr;
2664
0
2665
0
  return childContent;
2666
0
}
2667
2668
nsRange*
2669
nsFrameSelection::GetFirstCellRange()
2670
0
{
2671
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
2672
0
  if (!mDomSelections[index])
2673
0
    return nullptr;
2674
0
2675
0
  nsRange* firstRange = mDomSelections[index]->GetRangeAt(0);
2676
0
  if (!GetFirstCellNodeInRange(firstRange)) {
2677
0
    return nullptr;
2678
0
  }
2679
0
2680
0
  // Setup for next cell
2681
0
  mSelectedCellIndex = 1;
2682
0
2683
0
  return firstRange;
2684
0
}
2685
2686
nsRange*
2687
nsFrameSelection::GetNextCellRange()
2688
0
{
2689
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
2690
0
  if (!mDomSelections[index])
2691
0
    return nullptr;
2692
0
2693
0
  nsRange* range = mDomSelections[index]->GetRangeAt(mSelectedCellIndex);
2694
0
2695
0
  // Get first node in next range of selection - test if it's a cell
2696
0
  if (!GetFirstCellNodeInRange(range)) {
2697
0
    return nullptr;
2698
0
  }
2699
0
2700
0
  // Setup for next cell
2701
0
  mSelectedCellIndex++;
2702
0
2703
0
  return range;
2704
0
}
2705
2706
nsresult
2707
nsFrameSelection::GetCellIndexes(nsIContent *aCell,
2708
                                 int32_t    &aRowIndex,
2709
                                 int32_t    &aColIndex)
2710
0
{
2711
0
  if (!aCell) return NS_ERROR_NULL_POINTER;
2712
0
2713
0
  aColIndex=0; // initialize out params
2714
0
  aRowIndex=0;
2715
0
2716
0
  nsITableCellLayout *cellLayoutObject = GetCellLayout(aCell);
2717
0
  if (!cellLayoutObject)  return NS_ERROR_FAILURE;
2718
0
  return cellLayoutObject->GetCellIndexes(aRowIndex, aColIndex);
2719
0
}
2720
2721
nsIContent*
2722
nsFrameSelection::IsInSameTable(nsIContent  *aContent1,
2723
                                nsIContent  *aContent2) const
2724
0
{
2725
0
  if (!aContent1 || !aContent2) return nullptr;
2726
0
2727
0
  nsIContent* tableNode1 = GetParentTable(aContent1);
2728
0
  nsIContent* tableNode2 = GetParentTable(aContent2);
2729
0
2730
0
  // Must be in the same table.  Note that we want to return false for
2731
0
  // the test if both tables are null.
2732
0
  return (tableNode1 == tableNode2) ? tableNode1 : nullptr;
2733
0
}
2734
2735
nsIContent*
2736
nsFrameSelection::GetParentTable(nsIContent *aCell) const
2737
0
{
2738
0
  if (!aCell) {
2739
0
    return nullptr;
2740
0
  }
2741
0
2742
0
  for (nsIContent* parent = aCell->GetParent(); parent;
2743
0
       parent = parent->GetParent()) {
2744
0
    if (parent->IsHTMLElement(nsGkAtoms::table)) {
2745
0
      return parent;
2746
0
    }
2747
0
  }
2748
0
2749
0
  return nullptr;
2750
0
}
2751
2752
nsresult
2753
nsFrameSelection::SelectCellElement(nsIContent *aCellElement)
2754
0
{
2755
0
  nsIContent *parent = aCellElement->GetParent();
2756
0
2757
0
  // Get child offset
2758
0
  int32_t offset = parent->ComputeIndexOf(aCellElement);
2759
0
2760
0
  return CreateAndAddRange(parent, offset);
2761
0
}
2762
2763
nsresult
2764
nsFrameSelection::CreateAndAddRange(nsINode* aContainer, int32_t aOffset)
2765
0
{
2766
0
  if (!aContainer) {
2767
0
    return NS_ERROR_NULL_POINTER;
2768
0
  }
2769
0
2770
0
  RefPtr<nsRange> range = new nsRange(aContainer);
2771
0
2772
0
  // Set range around child at given offset
2773
0
  nsresult rv = range->SetStartAndEnd(aContainer, aOffset,
2774
0
                                      aContainer, aOffset + 1);
2775
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
2776
0
    return rv;
2777
0
  }
2778
0
2779
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
2780
0
  if (!mDomSelections[index])
2781
0
    return NS_ERROR_NULL_POINTER;
2782
0
2783
0
  ErrorResult err;
2784
0
  mDomSelections[index]->AddRange(*range, err);
2785
0
  return err.StealNSResult();
2786
0
}
2787
2788
// End of Table Selection
2789
2790
void
2791
nsFrameSelection::SetAncestorLimiter(nsIContent *aLimiter)
2792
0
{
2793
0
  if (mAncestorLimiter != aLimiter) {
2794
0
    mAncestorLimiter = aLimiter;
2795
0
    int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
2796
0
    if (!mDomSelections[index])
2797
0
      return;
2798
0
2799
0
    if (!IsValidSelectionPoint(this, mDomSelections[index]->GetFocusNode())) {
2800
0
      ClearNormalSelection();
2801
0
      if (mAncestorLimiter) {
2802
0
        PostReason(nsISelectionListener::NO_REASON);
2803
0
        TakeFocus(mAncestorLimiter, 0, 0, CARET_ASSOCIATE_BEFORE, false, false);
2804
0
      }
2805
0
    }
2806
0
  }
2807
0
}
2808
2809
nsresult
2810
nsFrameSelection::DeleteFromDocument()
2811
0
{
2812
0
  // If we're already collapsed, then we do nothing (bug 719503).
2813
0
  int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
2814
0
  if (!mDomSelections[index])
2815
0
    return NS_ERROR_NULL_POINTER;
2816
0
2817
0
  if (mDomSelections[index]->IsCollapsed())
2818
0
  {
2819
0
    return NS_OK;
2820
0
  }
2821
0
2822
0
  RefPtr<Selection> selection = mDomSelections[index];
2823
0
  for (uint32_t rangeIdx = 0; rangeIdx < selection->RangeCount(); ++rangeIdx) {
2824
0
    RefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
2825
0
    ErrorResult res;
2826
0
    range->DeleteContents(res);
2827
0
    if (res.Failed()) {
2828
0
      return res.StealNSResult();
2829
0
    }
2830
0
  }
2831
0
2832
0
  // Collapse to the new location.
2833
0
  // If we deleted one character, then we move back one element.
2834
0
  // FIXME  We don't know how to do this past frame boundaries yet.
2835
0
  if (mDomSelections[index]->AnchorOffset() > 0)
2836
0
    mDomSelections[index]->Collapse(mDomSelections[index]->GetAnchorNode(), mDomSelections[index]->AnchorOffset());
2837
#ifdef DEBUG
2838
  else
2839
    printf("Don't know how to set selection back past frame boundary\n");
2840
#endif
2841
2842
0
  return NS_OK;
2843
0
}
2844
2845
void
2846
nsFrameSelection::SetDelayedCaretData(WidgetMouseEvent* aMouseEvent)
2847
0
{
2848
0
  if (aMouseEvent) {
2849
0
    mDelayedMouseEventValid = true;
2850
0
    mDelayedMouseEventIsShift = aMouseEvent->IsShift();
2851
0
    mDelayedMouseEventClickCount = aMouseEvent->mClickCount;
2852
0
  } else {
2853
0
    mDelayedMouseEventValid = false;
2854
0
  }
2855
0
}
2856
2857
void
2858
nsFrameSelection::DisconnectFromPresShell()
2859
0
{
2860
0
  if (mAccessibleCaretEnabled) {
2861
0
    int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
2862
0
    mDomSelections[index]->StopNotifyingAccessibleCaretEventHub();
2863
0
  }
2864
0
2865
0
  StopAutoScrollTimer();
2866
0
  for (size_t i = 0; i < ArrayLength(mDomSelections); i++) {
2867
0
    mDomSelections[i]->Clear(nullptr);
2868
0
  }
2869
0
  mShell = nullptr;
2870
0
}
2871
2872
/**
2873
 * See Bug 1288453.
2874
 *
2875
 * Update the selection cache on repaint to handle when a pre-existing
2876
 * selection becomes active aka the current selection.
2877
 *
2878
 * 1. Change the current selection by click n dragging another selection.
2879
 *   - Make a selection on content page. Make a selection in a text editor.
2880
 *   - You can click n drag the content selection to make it active again.
2881
 * 2. Change the current selection when switching to a tab with a selection.
2882
 *   - Make selection in tab.
2883
 *   - Switching tabs will make its respective selection active.
2884
 *
2885
 * Therefore, we only update the selection cache on a repaint
2886
 * if the current selection being repainted is not an empty selection.
2887
 *
2888
 * If the current selection is empty. The current selection cache
2889
 * would be cleared by AutoCopyListener::OnSelectionChange().
2890
 */
2891
nsresult
2892
nsFrameSelection::UpdateSelectionCacheOnRepaintSelection(Selection* aSel)
2893
0
{
2894
0
  nsIPresShell* ps = aSel->GetPresShell();
2895
0
  if (!ps) {
2896
0
    return NS_OK;
2897
0
  }
2898
0
  nsCOMPtr<nsIDocument> aDoc = ps->GetDocument();
2899
0
2900
0
  if (aDoc && aSel && !aSel->IsCollapsed()) {
2901
0
    return nsCopySupport::HTMLCopy(aSel, aDoc,
2902
0
                                   nsIClipboard::kSelectionCache, false);
2903
0
  }
2904
0
2905
0
  return NS_OK;
2906
0
}
2907
2908
// mozilla::AutoCopyListener
2909
2910
int16_t AutoCopyListener::sClipboardID = -1;
2911
2912
/*
2913
 * What we do now:
2914
 * On every selection change, we copy to the clipboard anew, creating a
2915
 * HTML buffer, a transferable, an nsISupportsString and
2916
 * a huge mess every time.  This is basically what nsPresShell::DoCopy does
2917
 * to move the selection into the clipboard for Edit->Copy.
2918
 *
2919
 * What we should do, to make our end of the deal faster:
2920
 * Create a singleton transferable with our own magic converter.  When selection
2921
 * changes (use a quick cache to detect ``real'' changes), we put the new
2922
 * Selection in the transferable.  Our magic converter will take care of
2923
 * transferable->whatever-other-format when the time comes to actually
2924
 * hand over the clipboard contents.
2925
 *
2926
 * Other issues:
2927
 * - which X clipboard should we populate?
2928
 * - should we use a different one than Edit->Copy, so that inadvertant
2929
 *   selections (or simple clicks, which currently cause a selection
2930
 *   notification, regardless of if they're in the document which currently has
2931
 *   selection!) don't lose the contents of the ``application''?  Or should we
2932
 *   just put some intelligence in the ``is this a real selection?'' code to
2933
 *   protect our selection against clicks in other documents that don't create
2934
 *   selections?
2935
 * - maybe we should just never clear the X clipboard?  That would make this
2936
 *   problem just go away, which is very tempting.
2937
 *
2938
 * On macOS,
2939
 * nsIClipboard::kSelectionCache is the flag for current selection cache.
2940
 * Set the current selection cache on the parent process in
2941
 * widget cocoa nsClipboard whenever selection changes.
2942
 */
2943
2944
// static
2945
void
2946
AutoCopyListener::OnSelectionChange(nsIDocument* aDocument,
2947
                                    Selection& aSelection,
2948
                                    int16_t aReason)
2949
0
{
2950
0
  MOZ_ASSERT(IsValidClipboardID(sClipboardID));
2951
0
2952
0
  if (sClipboardID == nsIClipboard::kSelectionCache) {
2953
0
    nsFocusManager* fm = nsFocusManager::GetFocusManager();
2954
0
    // If no active window, do nothing because a current selection changed
2955
0
    // cannot occur unless it is in the active window.
2956
0
    if (!fm->GetActiveWindow()) {
2957
0
      return;
2958
0
    }
2959
0
  }
2960
0
2961
0
  static const int16_t kResasonsToHandle =
2962
0
    nsISelectionListener::MOUSEUP_REASON |
2963
0
    nsISelectionListener::SELECTALL_REASON |
2964
0
    nsISelectionListener::KEYPRESS_REASON;
2965
0
  if (!(aReason & kResasonsToHandle)) {
2966
0
    return; // Don't care if we are still dragging.
2967
0
  }
2968
0
2969
0
  if (!aDocument || aSelection.IsCollapsed()) {
2970
#ifdef DEBUG_CLIPBOARD
2971
    fprintf(stderr, "CLIPBOARD: no selection/collapsed selection\n");
2972
#endif
2973
0
    if (sClipboardID != nsIClipboard::kSelectionCache) {
2974
0
      // XXX Should we clear X clipboard?
2975
0
      return;
2976
0
    }
2977
0
2978
0
    // If on macOS, clear the current selection transferable cached
2979
0
    // on the parent process (nsClipboard) when the selection is empty.
2980
0
    DebugOnly<nsresult> rv = nsCopySupport::ClearSelectionCache();
2981
0
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2982
0
      "nsCopySupport::ClearSelectionCache() failed");
2983
0
    return;
2984
0
  }
2985
0
2986
0
  // Call the copy code.
2987
0
  DebugOnly<nsresult> rv =
2988
0
    nsCopySupport::HTMLCopy(&aSelection, aDocument, sClipboardID, false);
2989
0
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2990
0
    "nsCopySupport::HTMLCopy() failed");
2991
0
}