Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=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 file,
5 : * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #ifndef mozilla_Selection_h__
8 : #define mozilla_Selection_h__
9 :
10 : #include "nsIWeakReference.h"
11 :
12 : #include "mozilla/AutoRestore.h"
13 : #include "mozilla/TextRange.h"
14 : #include "mozilla/UniquePtr.h"
15 : #include "nsISelection.h"
16 : #include "nsISelectionController.h"
17 : #include "nsISelectionListener.h"
18 : #include "nsISelectionPrivate.h"
19 : #include "nsRange.h"
20 : #include "nsThreadUtils.h"
21 : #include "nsWrapperCache.h"
22 :
23 : struct CachedOffsetForFrame;
24 : class nsAutoScrollTimer;
25 : class nsIContentIterator;
26 : class nsIDocument;
27 : class nsIEditor;
28 : class nsIFrame;
29 : class nsIHTMLEditor;
30 : class nsFrameSelection;
31 : class nsPIDOMWindowOuter;
32 : struct SelectionDetails;
33 : struct SelectionCustomColors;
34 : class nsCopySupport;
35 : class nsHTMLCopyEncoder;
36 :
37 : namespace mozilla {
38 : class ErrorResult;
39 : struct AutoPrepareFocusRange;
40 : } // namespace mozilla
41 :
42 71 : struct RangeData
43 : {
44 41 : explicit RangeData(nsRange* aRange)
45 41 : : mRange(aRange)
46 41 : {}
47 :
48 : RefPtr<nsRange> mRange;
49 : mozilla::TextRangeStyle mTextRangeStyle;
50 : };
51 :
52 : // Note, the ownership of mozilla::dom::Selection depends on which way the
53 : // object is created. When nsFrameSelection has created Selection,
54 : // addreffing/releasing the Selection object is aggregated to nsFrameSelection.
55 : // Otherwise normal addref/release is used. This ensures that nsFrameSelection
56 : // is never deleted before its Selections.
57 : namespace mozilla {
58 : namespace dom {
59 :
60 : class Selection final : public nsISelectionPrivate,
61 : public nsWrapperCache,
62 : public nsSupportsWeakReference
63 : {
64 : protected:
65 : virtual ~Selection();
66 :
67 : public:
68 : Selection();
69 : explicit Selection(nsFrameSelection *aList);
70 :
71 : NS_DECL_CYCLE_COLLECTING_ISUPPORTS
72 1448 : NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Selection, nsISelectionPrivate)
73 : NS_DECL_NSISELECTION
74 : NS_DECL_NSISELECTIONPRIVATE
75 :
76 48 : virtual Selection* AsSelection() override { return this; }
77 :
78 : nsresult EndBatchChangesInternal(int16_t aReason = nsISelectionListener::NO_REASON);
79 :
80 : nsIDocument* GetParentObject() const;
81 :
82 : // utility methods for scrolling the selection into view
83 : nsPresContext* GetPresContext() const;
84 : nsIPresShell* GetPresShell() const;
85 9 : nsFrameSelection* GetFrameSelection() const { return mFrameSelection; }
86 : // Returns a rect containing the selection region, and frame that that
87 : // position is relative to. For SELECTION_ANCHOR_REGION or
88 : // SELECTION_FOCUS_REGION the rect is a zero-width rectangle. For
89 : // SELECTION_WHOLE_SELECTION the rect contains both the anchor and focus
90 : // region rects.
91 : nsIFrame* GetSelectionAnchorGeometry(SelectionRegion aRegion, nsRect *aRect);
92 : // Returns the position of the region (SELECTION_ANCHOR_REGION or
93 : // SELECTION_FOCUS_REGION only), and frame that that position is relative to.
94 : // The 'position' is a zero-width rectangle.
95 : nsIFrame* GetSelectionEndPointGeometry(SelectionRegion aRegion, nsRect *aRect);
96 :
97 : nsresult PostScrollSelectionIntoViewEvent(
98 : SelectionRegion aRegion,
99 : int32_t aFlags,
100 : nsIPresShell::ScrollAxis aVertical,
101 : nsIPresShell::ScrollAxis aHorizontal);
102 : enum {
103 : SCROLL_SYNCHRONOUS = 1<<1,
104 : SCROLL_FIRST_ANCESTOR_ONLY = 1<<2,
105 : SCROLL_DO_FLUSH = 1<<3, // only matters if SCROLL_SYNCHRONOUS is passed too
106 : SCROLL_OVERFLOW_HIDDEN = 1<<5,
107 : SCROLL_FOR_CARET_MOVE = 1<<6
108 : };
109 : // If aFlags doesn't contain SCROLL_SYNCHRONOUS, then we'll flush when
110 : // the scroll event fires so we make sure to scroll to the right place.
111 : // Otherwise, if SCROLL_DO_FLUSH is also in aFlags, then this method will
112 : // flush layout and you MUST hold a strong ref on 'this' for the duration
113 : // of this call. This might destroy arbitrary layout objects.
114 : nsresult ScrollIntoView(SelectionRegion aRegion,
115 : nsIPresShell::ScrollAxis aVertical =
116 : nsIPresShell::ScrollAxis(),
117 : nsIPresShell::ScrollAxis aHorizontal =
118 : nsIPresShell::ScrollAxis(),
119 : int32_t aFlags = 0);
120 : nsresult SubtractRange(RangeData* aRange, nsRange* aSubtract,
121 : nsTArray<RangeData>* aOutput);
122 : /**
123 : * AddItem adds aRange to this Selection. If mUserInitiated is true,
124 : * then aRange is first scanned for -moz-user-select:none nodes and split up
125 : * into multiple ranges to exclude those before adding the resulting ranges
126 : * to this Selection.
127 : */
128 : nsresult AddItem(nsRange* aRange, int32_t* aOutIndex, bool aNoStartSelect = false);
129 : nsresult RemoveItem(nsRange* aRange);
130 : nsresult RemoveCollapsedRanges();
131 : nsresult Clear(nsPresContext* aPresContext);
132 : nsresult Collapse(nsINode* aContainer, int32_t aOffset);
133 : nsresult Extend(nsINode* aContainer, int32_t aOffset);
134 : nsRange* GetRangeAt(int32_t aIndex) const;
135 :
136 : // Get the anchor-to-focus range if we don't care which end is
137 : // anchor and which end is focus.
138 3 : const nsRange* GetAnchorFocusRange() const {
139 3 : return mAnchorFocusRange;
140 : }
141 :
142 2 : nsDirection GetDirection(){return mDirection;}
143 82 : void SetDirection(nsDirection aDir){mDirection = aDir;}
144 : nsresult SetAnchorFocusToRange(nsRange *aRange);
145 : void ReplaceAnchorFocusRange(nsRange *aRange);
146 : void AdjustAnchorFocusForMultiRange(nsDirection aDirection);
147 :
148 : // NS_IMETHOD GetPrimaryFrameForRangeEndpoint(nsIDOMNode* aContainer,
149 : // int32_t aOffset,
150 : // bool aIsEndNode,
151 : // nsIFrame** aResultFrame);
152 : NS_IMETHOD GetPrimaryFrameForAnchorNode(nsIFrame **aResultFrame);
153 : NS_IMETHOD GetPrimaryFrameForFocusNode(nsIFrame **aResultFrame, int32_t *aOffset, bool aVisual);
154 :
155 : UniquePtr<SelectionDetails> LookUpSelection(
156 : nsIContent* aContent,
157 : int32_t aContentOffset,
158 : int32_t aContentLength,
159 : UniquePtr<SelectionDetails> aDetailsHead,
160 : SelectionType aSelectionType,
161 : bool aSlowCheck);
162 :
163 : NS_IMETHOD Repaint(nsPresContext* aPresContext);
164 :
165 : // Note: StartAutoScrollTimer might destroy arbitrary frames etc.
166 : nsresult StartAutoScrollTimer(nsIFrame *aFrame,
167 : nsPoint& aPoint,
168 : uint32_t aDelay);
169 :
170 : nsresult StopAutoScrollTimer();
171 :
172 : JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
173 :
174 : // WebIDL methods
175 : nsINode* GetAnchorNode();
176 : uint32_t AnchorOffset();
177 : nsINode* GetFocusNode();
178 : uint32_t FocusOffset();
179 :
180 : bool IsCollapsed() const;
181 :
182 : // *JS() methods are mapped to Selection.*().
183 : // They may move focus only when the range represents normal selection.
184 : // These methods shouldn't be used by non-JS callers.
185 : void CollapseJS(nsINode* aContainer, uint32_t aOffset,
186 : mozilla::ErrorResult& aRv);
187 : void CollapseToStartJS(mozilla::ErrorResult& aRv);
188 : void CollapseToEndJS(mozilla::ErrorResult& aRv);
189 :
190 : void ExtendJS(nsINode& aContainer, uint32_t aOffset,
191 : mozilla::ErrorResult& aRv);
192 :
193 : void SelectAllChildrenJS(nsINode& aNode, mozilla::ErrorResult& aRv);
194 :
195 : void DeleteFromDocument(mozilla::ErrorResult& aRv);
196 :
197 75 : uint32_t RangeCount() const
198 : {
199 75 : return mRanges.Length();
200 : }
201 : nsRange* GetRangeAt(uint32_t aIndex, mozilla::ErrorResult& aRv);
202 : void AddRangeJS(nsRange& aRange, mozilla::ErrorResult& aRv);
203 : void RemoveRange(nsRange& aRange, mozilla::ErrorResult& aRv);
204 : void RemoveAllRanges(mozilla::ErrorResult& aRv);
205 :
206 : void Stringify(nsAString& aResult);
207 :
208 : bool ContainsNode(nsINode& aNode, bool aPartlyContained, mozilla::ErrorResult& aRv);
209 :
210 : /**
211 : * Check to see if the given point is contained within the selection area. In
212 : * particular, this iterates through all the rects that make up the selection,
213 : * not just the bounding box, and checks to see if the given point is contained
214 : * in any one of them.
215 : * @param aPoint The point to check, relative to the root frame.
216 : */
217 : bool ContainsPoint(const nsPoint& aPoint);
218 :
219 : void Modify(const nsAString& aAlter, const nsAString& aDirection,
220 : const nsAString& aGranularity, mozilla::ErrorResult& aRv);
221 :
222 : void SetBaseAndExtentJS(nsINode& aAnchorNode, uint32_t aAnchorOffset,
223 : nsINode& aFocusNode, uint32_t aFocusOffset,
224 : mozilla::ErrorResult& aRv);
225 :
226 : bool GetInterlinePosition(mozilla::ErrorResult& aRv);
227 : void SetInterlinePosition(bool aValue, mozilla::ErrorResult& aRv);
228 :
229 : Nullable<int16_t> GetCaretBidiLevel(mozilla::ErrorResult& aRv) const;
230 : void SetCaretBidiLevel(const Nullable<int16_t>& aCaretBidiLevel, mozilla::ErrorResult& aRv);
231 :
232 : void ToStringWithFormat(const nsAString& aFormatType,
233 : uint32_t aFlags,
234 : int32_t aWrapColumn,
235 : nsAString& aReturn,
236 : mozilla::ErrorResult& aRv);
237 : void AddSelectionListener(nsISelectionListener* aListener,
238 : mozilla::ErrorResult& aRv);
239 : void RemoveSelectionListener(nsISelectionListener* aListener,
240 : mozilla::ErrorResult& aRv);
241 :
242 0 : RawSelectionType RawType() const
243 : {
244 0 : return ToRawSelectionType(mSelectionType);
245 : }
246 0 : SelectionType Type() const { return mSelectionType; }
247 :
248 : void GetRangesForInterval(nsINode& aBeginNode, int32_t aBeginOffset,
249 : nsINode& aEndNode, int32_t aEndOffset,
250 : bool aAllowAdjacent,
251 : nsTArray<RefPtr<nsRange>>& aReturn,
252 : mozilla::ErrorResult& aRv);
253 :
254 : void ScrollIntoView(int16_t aRegion, bool aIsSynchronous,
255 : int16_t aVPercent, int16_t aHPercent,
256 : mozilla::ErrorResult& aRv);
257 :
258 : void SetColors(const nsAString& aForeColor, const nsAString& aBackColor,
259 : const nsAString& aAltForeColor, const nsAString& aAltBackColor,
260 : mozilla::ErrorResult& aRv);
261 :
262 : void ResetColors(mozilla::ErrorResult& aRv);
263 :
264 : // Non-JS callers should use the following methods.
265 : void Collapse(nsINode& aContainer, uint32_t aOffset, ErrorResult& aRv);
266 : void CollapseToStart(mozilla::ErrorResult& aRv);
267 : void CollapseToEnd(mozilla::ErrorResult& aRv);
268 : void Extend(nsINode& aContainer, uint32_t aOffset, ErrorResult& aRv);
269 : void AddRange(nsRange& aRange, mozilla::ErrorResult& aRv);
270 : void SelectAllChildren(nsINode& aNode, mozilla::ErrorResult& aRv);
271 : void SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset,
272 : nsINode& aFocusNode, uint32_t aFocusOffset,
273 : mozilla::ErrorResult& aRv);
274 :
275 : void AddSelectionChangeBlocker();
276 : void RemoveSelectionChangeBlocker();
277 : bool IsBlockingSelectionChangeEvents() const;
278 : private:
279 : friend class ::nsAutoScrollTimer;
280 :
281 : // Note: DoAutoScroll might destroy arbitrary frames etc.
282 : nsresult DoAutoScroll(nsIFrame *aFrame, nsPoint& aPoint);
283 :
284 : // XXX Please don't add additional uses of this method, it's only for
285 : // XXX supporting broken code (bug 1245883) in the following classes:
286 : friend class ::nsCopySupport;
287 : friend class ::nsHTMLCopyEncoder;
288 : void AddRangeInternal(nsRange& aRange, nsIDocument* aDocument, ErrorResult&);
289 :
290 : public:
291 32 : SelectionType GetType() const { return mSelectionType; }
292 320 : void SetType(SelectionType aSelectionType)
293 : {
294 320 : mSelectionType = aSelectionType;
295 320 : }
296 :
297 0 : SelectionCustomColors* GetCustomColors() const { return mCustomColors.get(); }
298 :
299 : nsresult NotifySelectionListeners(bool aCalledByJS);
300 : nsresult NotifySelectionListeners();
301 :
302 : friend struct AutoUserInitiated;
303 0 : struct MOZ_RAII AutoUserInitiated
304 : {
305 0 : explicit AutoUserInitiated(Selection* aSelection
306 : MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
307 0 : : mSavedValue(aSelection->mUserInitiated)
308 : {
309 0 : MOZ_GUARD_OBJECT_NOTIFIER_INIT;
310 0 : aSelection->mUserInitiated = true;
311 0 : }
312 : AutoRestore<bool> mSavedValue;
313 : MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
314 : };
315 :
316 : private:
317 : friend struct mozilla::AutoPrepareFocusRange;
318 : class ScrollSelectionIntoViewEvent;
319 : friend class ScrollSelectionIntoViewEvent;
320 :
321 6 : class ScrollSelectionIntoViewEvent : public Runnable {
322 : public:
323 : NS_DECL_NSIRUNNABLE
324 2 : ScrollSelectionIntoViewEvent(Selection* aSelection,
325 : SelectionRegion aRegion,
326 : nsIPresShell::ScrollAxis aVertical,
327 : nsIPresShell::ScrollAxis aHorizontal,
328 : int32_t aFlags)
329 2 : : Runnable("dom::Selection::ScrollSelectionIntoViewEvent")
330 : , mSelection(aSelection)
331 : , mRegion(aRegion)
332 : , mVerticalScroll(aVertical)
333 : , mHorizontalScroll(aHorizontal)
334 2 : , mFlags(aFlags)
335 : {
336 2 : NS_ASSERTION(aSelection, "null parameter");
337 2 : }
338 1 : void Revoke() { mSelection = nullptr; }
339 : private:
340 : Selection *mSelection;
341 : SelectionRegion mRegion;
342 : nsIPresShell::ScrollAxis mVerticalScroll;
343 : nsIPresShell::ScrollAxis mHorizontalScroll;
344 : int32_t mFlags;
345 : };
346 :
347 : /**
348 : * Set mAnchorFocusRange to mRanges[aIndex] if aIndex is a valid index.
349 : * Set mAnchorFocusRange to nullptr if aIndex is negative.
350 : * Otherwise, i.e., if aIndex is positive but out of bounds of mRanges, do
351 : * nothing.
352 : */
353 : void SetAnchorFocusRange(int32_t aIndex);
354 : void SelectFramesForContent(nsIContent* aContent, bool aSelected);
355 : nsresult SelectAllFramesForContent(nsIContentIterator* aInnerIter,
356 : nsIContent *aContent,
357 : bool aSelected);
358 : nsresult SelectFrames(nsPresContext* aPresContext,
359 : nsRange* aRange,
360 : bool aSelect);
361 : nsresult GetTableCellLocationFromRange(nsRange* aRange,
362 : int32_t* aSelectionType,
363 : int32_t* aRow,
364 : int32_t* aCol);
365 : nsresult AddTableCellRange(nsRange* aRange,
366 : bool* aDidAddRange,
367 : int32_t* aOutIndex);
368 :
369 : nsresult FindInsertionPoint(
370 : nsTArray<RangeData>* aElementArray,
371 : nsINode* aPointNode, int32_t aPointOffset,
372 : nsresult (*aComparator)(nsINode*,int32_t,nsRange*,int32_t*),
373 : int32_t* aPoint);
374 : bool EqualsRangeAtPoint(nsINode* aBeginNode, int32_t aBeginOffset,
375 : nsINode* aEndNode, int32_t aEndOffset,
376 : int32_t aRangeIndex);
377 : nsresult GetIndicesForInterval(nsINode* aBeginNode, int32_t aBeginOffset,
378 : nsINode* aEndNode, int32_t aEndOffset,
379 : bool aAllowAdjacent,
380 : int32_t* aStartIndex, int32_t* aEndIndex);
381 : RangeData* FindRangeData(nsIDOMRange* aRange);
382 :
383 : void UserSelectRangesToAdd(nsRange* aItem, nsTArray<RefPtr<nsRange> >& rangesToAdd);
384 :
385 : /**
386 : * Helper method for AddItem.
387 : */
388 : nsresult AddItemInternal(nsRange* aRange, int32_t* aOutIndex);
389 :
390 : nsIDocument* GetDocument() const;
391 : nsPIDOMWindowOuter* GetWindow() const;
392 : nsIEditor* GetEditor() const;
393 :
394 : /**
395 : * GetCommonEditingHostForAllRanges() returns common editing host of all
396 : * ranges if there is. If at least one of the ranges is in non-editable
397 : * element, returns nullptr. See following examples for the detail:
398 : *
399 : * <div id="a" contenteditable>
400 : * an[cestor
401 : * <div id="b" contenteditable="false">
402 : * non-editable
403 : * <div id="c" contenteditable>
404 : * desc]endant
405 : * in this case, this returns div#a because div#c is also in div#a.
406 : *
407 : * <div id="a" contenteditable>
408 : * an[ce]stor
409 : * <div id="b" contenteditable="false">
410 : * non-editable
411 : * <div id="c" contenteditable>
412 : * de[sc]endant
413 : * in this case, this returns div#a because second range is also in div#a
414 : * and common ancestor of the range (i.e., div#c) is editable.
415 : *
416 : * <div id="a" contenteditable>
417 : * an[ce]stor
418 : * <div id="b" contenteditable="false">
419 : * [non]-editable
420 : * <div id="c" contenteditable>
421 : * de[sc]endant
422 : * in this case, this returns nullptr because the second range is in
423 : * non-editable area.
424 : */
425 : Element* GetCommonEditingHostForAllRanges();
426 :
427 : // These are the ranges inside this selection. They are kept sorted in order
428 : // of DOM start position.
429 : //
430 : // This data structure is sorted by the range beginnings. As the ranges are
431 : // disjoint, it is also implicitly sorted by the range endings. This allows
432 : // us to perform binary searches when searching for existence of a range,
433 : // giving us O(log n) search time.
434 : //
435 : // Inserting a new range requires finding the overlapping interval, requiring
436 : // two binary searches plus up to an additional 6 DOM comparisons. If this
437 : // proves to be a performance concern, then an interval tree may be a
438 : // possible solution, allowing the calculation of the overlap interval in
439 : // O(log n) time, though this would require rebalancing and other overhead.
440 : nsTArray<RangeData> mRanges;
441 :
442 : RefPtr<nsRange> mAnchorFocusRange;
443 : RefPtr<nsFrameSelection> mFrameSelection;
444 : RefPtr<nsAutoScrollTimer> mAutoScrollTimer;
445 : FallibleTArray<nsCOMPtr<nsISelectionListener>> mSelectionListeners;
446 : nsRevocableEventPtr<ScrollSelectionIntoViewEvent> mScrollEvent;
447 : CachedOffsetForFrame* mCachedOffsetForFrame;
448 : nsDirection mDirection;
449 : SelectionType mSelectionType;
450 : UniquePtr<SelectionCustomColors> mCustomColors;
451 :
452 : /**
453 : * True if the current selection operation was initiated by user action.
454 : * It determines whether we exclude -moz-user-select:none nodes or not,
455 : * as well as whether selectstart events will be fired.
456 : */
457 : bool mUserInitiated;
458 :
459 : /**
460 : * When the selection change is caused by a call of Selection API,
461 : * mCalledByJS is true. Otherwise, false.
462 : */
463 : bool mCalledByJS;
464 :
465 : // Non-zero if we don't want any changes we make to the selection to be
466 : // visible to content. If non-zero, content won't be notified about changes.
467 : uint32_t mSelectionChangeBlockerCount;
468 : };
469 :
470 : // Stack-class to turn on/off selection batching.
471 : class MOZ_STACK_CLASS SelectionBatcher final
472 : {
473 : private:
474 : RefPtr<Selection> mSelection;
475 : public:
476 1 : explicit SelectionBatcher(Selection* aSelection)
477 1 : {
478 1 : mSelection = aSelection;
479 1 : if (mSelection) {
480 1 : mSelection->StartBatchChanges();
481 : }
482 1 : }
483 :
484 1 : ~SelectionBatcher()
485 1 : {
486 1 : if (mSelection) {
487 1 : mSelection->EndBatchChangesInternal();
488 : }
489 1 : }
490 : };
491 :
492 : class MOZ_RAII AutoHideSelectionChanges final
493 : {
494 : private:
495 : RefPtr<Selection> mSelection;
496 : MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
497 : public:
498 : explicit AutoHideSelectionChanges(const nsFrameSelection* aFrame);
499 :
500 6 : explicit AutoHideSelectionChanges(Selection* aSelection
501 : MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
502 6 : : mSelection(aSelection)
503 : {
504 6 : MOZ_GUARD_OBJECT_NOTIFIER_INIT;
505 6 : mSelection = aSelection;
506 6 : if (mSelection) {
507 6 : mSelection->AddSelectionChangeBlocker();
508 : }
509 6 : }
510 :
511 6 : ~AutoHideSelectionChanges()
512 6 : {
513 6 : if (mSelection) {
514 6 : mSelection->RemoveSelectionChangeBlocker();
515 : }
516 6 : }
517 : };
518 :
519 : } // namespace dom
520 : } // namespace mozilla
521 :
522 : #endif // mozilla_Selection_h__
|