layout/generic/nsFrameSelection.h
author Sylvestre Ledru <sledru@mozilla.com>
Tue, 27 Nov 2018 21:39:24 +0000
changeset 504815 af951294cf96812660b2ac9918eb6df32758d420
parent 504393 8e88421b280c2afda62f4ba704ce29701c30549f
child 504817 193a2a5ad0e6148fa266929af17ff2d897af9f12
permissions -rw-r--r--
Bug 1508472 - Part 3: Third batch of comment fix-ups in preparation for the tree reformat (layout/) r=Ehsan Differential Revision: https://phabricator.services.mozilla.com/D13125

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef nsFrameSelection_h___
#define nsFrameSelection_h___

#include "mozilla/Attributes.h"
#include "mozilla/EventForwards.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/TextRange.h"
#include "mozilla/UniquePtr.h"
#include "nsIFrame.h"
#include "nsIContent.h"
#include "nsISelectionController.h"
#include "nsISelectionListener.h"
#include "nsITableCellLayout.h"
#include "WordMovementType.h"
#include "CaretAssociationHint.h"
#include "nsBidiPresUtils.h"

class nsRange;

#define BIDI_LEVEL_UNDEFINED 0x80

//----------------------------------------------------------------------

// Selection interface

struct SelectionDetails
{
  SelectionDetails()
    : mStart()
    , mEnd()
    , mSelectionType(mozilla::SelectionType::eInvalid) {
    MOZ_COUNT_CTOR(SelectionDetails);
  }
#ifdef NS_BUILD_REFCNT_LOGGING
  ~SelectionDetails() {
    MOZ_COUNT_DTOR(SelectionDetails);
  }
#endif
  int32_t mStart;
  int32_t mEnd;
  mozilla::SelectionType mSelectionType;
  mozilla::TextRangeStyle mTextRangeStyle;
  mozilla::UniquePtr<SelectionDetails> mNext;
};

struct SelectionCustomColors
{
#ifdef NS_BUILD_REFCNT_LOGGING
  SelectionCustomColors()
  {
    MOZ_COUNT_CTOR(SelectionCustomColors);
  }
  ~SelectionCustomColors()
  {
    MOZ_COUNT_DTOR(SelectionCustomColors);
  }
#endif
  mozilla::Maybe<nscolor> mForegroundColor;
  mozilla::Maybe<nscolor> mBackgroundColor;
  mozilla::Maybe<nscolor> mAltForegroundColor;
  mozilla::Maybe<nscolor> mAltBackgroundColor;
};

class nsIPresShell;

/** PeekOffsetStruct is used to group various arguments (both input and output)
 *  that are passed to nsFrame::PeekOffset(). See below for the description of
 *  individual arguments.
 */
struct MOZ_STACK_CLASS nsPeekOffsetStruct
{
  enum class ForceEditableRegion
  {
    No,
    Yes,
  };

  nsPeekOffsetStruct(nsSelectionAmount aAmount,
                     nsDirection aDirection,
                     int32_t aStartOffset,
                     nsPoint aDesiredPos,
                     bool aJumpLines,
                     bool aScrollViewStop,
                     bool aIsKeyboardSelect,
                     bool aVisual,
                     bool aExtend,
                     ForceEditableRegion = ForceEditableRegion::No,
                     mozilla::EWordMovementType aWordMovementType = mozilla::eDefaultBehavior);

  // Note: Most arguments (input and output) are only used with certain values
  // of mAmount. These values are indicated for each argument below.
  // Arguments with no such indication are used with all values of mAmount.

  /*** Input arguments ***/
  // Note: The value of some of the input arguments may be changed upon exit.

  // mAmount: The type of movement requested (by character, word, line, etc.)
  nsSelectionAmount mAmount;

  // mDirection: eDirPrevious or eDirNext.
  //             * Note for visual bidi movement:
  //             eDirPrevious means 'left-then-up' if the containing block is LTR,
  //             'right-then-up' if it is RTL.
  //             eDirNext means 'right-then-down' if the containing block is LTR,
  //             'left-then-down' if it is RTL.
  //             Between paragraphs, eDirPrevious means "go to the visual end of the
  //             previous paragraph", and eDirNext means "go to the visual beginning
  //             of the next paragraph".
  //             Used with: eSelectCharacter, eSelectWord, eSelectLine, eSelectParagraph.
  nsDirection mDirection;

  // mStartOffset: Offset into the content of the current frame where the peek starts.
  //               Used with: eSelectCharacter, eSelectWord
  int32_t mStartOffset;

  // mDesiredPos: The desired inline coordinate for the caret
  //              (one of .x or .y will be used, depending on line's writing mode)
  //              Used with: eSelectLine.
  nsPoint mDesiredPos;

  // mWordMovementType: An enum that determines whether to prefer the start or end of a word
  //                    or to use the default beahvior, which is a combination of
  //                    direction and the platform-based pref
  //                    "layout.word_select.eat_space_to_next_word"
  mozilla::EWordMovementType mWordMovementType;

  // mJumpLines: Whether to allow jumping across line boundaries.
  //             Used with: eSelectCharacter, eSelectWord.
  bool mJumpLines;

  // mScrollViewStop: Whether to stop when reaching a scroll view boundary.
  //                  Used with: eSelectCharacter, eSelectWord, eSelectLine.
  bool mScrollViewStop;

  // mIsKeyboardSelect: Whether the peeking is done in response to a keyboard action.
  //                    Used with: eSelectWord.
  bool mIsKeyboardSelect;

  // mVisual: Whether bidi caret behavior is visual (true) or logical (false).
  //          Used with: eSelectCharacter, eSelectWord, eSelectBeginLine, eSelectEndLine.
  bool mVisual;

  // mExtend: Whether the selection is being extended or moved.
  bool mExtend;

  // mForceEditableRegion: If true, the offset has to end up in an editable
  // node, otherwise we'll keep searching.
  const bool mForceEditableRegion;

  /*** Output arguments ***/

  // mResultContent: Content reached as a result of the peek.
  nsCOMPtr<nsIContent> mResultContent;

  // mResultFrame: Frame reached as a result of the peek.
  //               Used with: eSelectCharacter, eSelectWord.
  nsIFrame* mResultFrame;

  // mContentOffset: Offset into content reached as a result of the peek.
  int32_t mContentOffset;

  // mAttachForward: When the result position is between two frames,
  //                 indicates which of the two frames the caret should be painted in.
  //                 false means "the end of the frame logically before the caret",
  //                 true means "the beginning of the frame logically after the caret".
  //                 Used with: eSelectLine, eSelectBeginLine, eSelectEndLine.
  mozilla::CaretAssociationHint mAttach;
};

struct nsPrevNextBidiLevels
{
  void SetData(nsIFrame* aFrameBefore,
               nsIFrame* aFrameAfter,
               nsBidiLevel aLevelBefore,
               nsBidiLevel aLevelAfter)
  {
    mFrameBefore = aFrameBefore;
    mFrameAfter = aFrameAfter;
    mLevelBefore = aLevelBefore;
    mLevelAfter = aLevelAfter;
  }
  nsIFrame* mFrameBefore;
  nsIFrame* mFrameAfter;
  nsBidiLevel mLevelBefore;
  nsBidiLevel mLevelAfter;
};

namespace mozilla {
class SelectionChangeEventDispatcher;
namespace dom {
class Selection;
} // namespace dom

/**
 * Constants for places that want to handle table selections.  These
 * indicate what part of a table is being selected.
 */
enum class TableSelection : uint32_t {
  None, /* Nothing being selected; not valid in all cases. */
  Cell, /* A cell is being selected. */
  Row,  /* A row is being selected. */
  Column, /* A column is being selected. */
  Table, /* A table (including cells and captions) is being selected. */
  AllCells, /* All the cells in a table are being selected. */
};

} // namespace mozilla
class nsIScrollableFrame;

/**
 * Methods which are marked with *unsafe* should be handled with special care.
 * They may cause nsFrameSelection to be deleted, if strong pointer isn't used,
 * or they may cause other objects to be deleted.
 */
class nsFrameSelection final
{
public:
  typedef mozilla::CaretAssociationHint CaretAssociateHint;

  /*interfaces for addref and release and queryinterface*/

  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsFrameSelection)
  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsFrameSelection)

  /** Init will initialize the frame selector with the necessary pres shell to
   *  be used by most of the methods
   *  @param aShell is the parameter to be used for most of the other calls for
   *         callbacks etc
   *  @param aLimiter limits the selection to nodes with aLimiter parents
   *  @param aAccessibleCaretEnabled true if we should enable the accessible
   *         caret.
   */
  void Init(nsIPresShell *aShell, nsIContent *aLimiter,
            bool aAccessibleCaretEnabled);

  /** HandleClick will take the focus to the new frame at the new offset and
   *  will either extend the selection from the old anchor, or replace the old
   *  anchor.
   *  the old anchor and focus position may also be used to deselect things
   *  @param aNewfocus is the content that wants the focus
   *  @param aContentOffset is the content offset of the parent aNewFocus
   *  @param aContentOffsetEnd is the content offset of the parent aNewFocus
   *                           and is specified different when you need to
   *                           select to and include both start and end points
   *  @param aContinueSelection is the flag that tells the selection to keep
   *                            the old anchor point or not.
   *  @param aMultipleSelection will tell the frame selector to replace /or not
   *                            the old selection.
   *                            cannot coexist with aContinueSelection
   *  @param aHint will tell the selection which direction geometrically to
   *               actually show the caret on.
   *         1 = end of this line 0 = beginning of this line
   */
  MOZ_CAN_RUN_SCRIPT_BOUNDARY
  nsresult HandleClick(nsIContent *aNewFocus,
                       uint32_t aContentOffset,
                       uint32_t aContentEndOffset,
                       bool aContinueSelection,
                       bool aMultipleSelection,
                       CaretAssociateHint aHint);

  /** HandleDrag extends the selection to contain the frame closest to aPoint.
   *  @param aPresContext is the context to use when figuring out what frame
   *                      contains the point.
   *  @param aFrame is the parent of all frames to use when searching for the
   *                closest frame to the point.
   *  @param aPoint is relative to aFrame
   */
  /*unsafe*/
  void HandleDrag(nsIFrame* aFrame, const nsPoint& aPoint);

  /** HandleTableSelection will set selection to a table, cell, etc
   *   depending on information contained in aFlags
   *  @param aParentContent is the parent of either a table or cell that
   *                        user clicked or dragged the mouse in
   *  @param aContentOffset is the offset of the table or cell
   *  @param aTarget indicates what to select
   *    TableSelection::Cell     We should select a cell
   *                             (content points to the cell)
   *    TableSelection::Row      We should select a row
   *                             (content points to any cell in row)
   *    TableSelection::Column   We should select a row
   *                             (content points to any cell in column)
   *    TableSelection::Table    We should select a table
   *                             (content points to the table)
   *    TableSelection::AllCells We should select all cells
   *                             (content points to any cell in table)
   *  @param aMouseEvent         passed in so we can get where event occurred
   *                             and what keys are pressed
   */
  /*unsafe*/
  nsresult HandleTableSelection(nsINode* aParentContent,
                                int32_t aContentOffset,
                                mozilla::TableSelection aTarget,
                                mozilla::WidgetMouseEvent* aMouseEvent);

  /**
   * Add cell to the selection.
   *
   * @param  aCell  [in] HTML td element.
   */
  nsresult SelectCellElement(nsIContent *aCell);

  /**
   * Add cells to the selection inside of the given cells range.
   *
   * @param  aTable             [in] HTML table element
   * @param  aStartRowIndex     [in] row index where the cells range starts
   * @param  aStartColumnIndex  [in] column index where the cells range starts
   * @param  aEndRowIndex       [in] row index where the cells range ends
   * @param  aEndColumnIndex    [in] column index where the cells range ends
   */
  nsresult AddCellsToSelection(nsIContent* aTable,
                               int32_t aStartRowIndex,
                               int32_t aStartColumnIndex,
                               int32_t aEndRowIndex,
                               int32_t aEndColumnIndex);

  /**
   * Remove cells from selection inside of the given cell range.
   *
   * @param  aTable             [in] HTML table element
   * @param  aStartRowIndex     [in] row index where the cells range starts
   * @param  aStartColumnIndex  [in] column index where the cells range starts
   * @param  aEndRowIndex       [in] row index where the cells range ends
   * @param  aEndColumnIndex    [in] column index where the cells range ends
   */
  nsresult RemoveCellsFromSelection(nsIContent* aTable,
                                    int32_t aStartRowIndex,
                                    int32_t aStartColumnIndex,
                                    int32_t aEndRowIndex,
                                    int32_t aEndColumnIndex);

  /**
   * Remove cells from selection outside of the given cell range.
   *
   * @param  aTable             [in] HTML table element
   * @param  aStartRowIndex     [in] row index where the cells range starts
   * @param  aStartColumnIndex  [in] column index where the cells range starts
   * @param  aEndRowIndex       [in] row index where the cells range ends
   * @param  aEndColumnIndex    [in] column index where the cells range ends
   */
  nsresult RestrictCellsToSelection(nsIContent* aTable,
                                    int32_t aStartRowIndex,
                                    int32_t aStartColumnIndex,
                                    int32_t aEndRowIndex,
                                    int32_t aEndColumnIndex);

  /** StartAutoScrollTimer is responsible for scrolling frames so that
   *  aPoint is always visible, and for selecting any frame that contains
   *  aPoint. The timer will also reset itself to fire again if we have
   *  not scrolled to the end of the document.
   *  @param aFrame is the outermost frame to use when searching for
   *  the closest frame for the point, i.e. the frame that is capturing
   *  the mouse
   *  @param aPoint is relative to aFrame.
   *  @param aDelay is the timer's interval.
   */
  /*unsafe*/
  nsresult StartAutoScrollTimer(nsIFrame* aFrame,
                                const nsPoint& aPoint,
                                uint32_t aDelay);

  /** StopAutoScrollTimer stops any active auto scroll timer.
   */
  void StopAutoScrollTimer();

  /** Lookup Selection
   *  returns in frame coordinates the selection beginning and ending with the
   *  type of selection given
   * @param aContent is the content asking
   * @param aContentOffset is the starting content boundary
   * @param aContentLength is the length of the content piece asking
   * @param aSlowCheck will check using slow method with no shortcuts
   */
  mozilla::UniquePtr<SelectionDetails> LookUpSelection(nsIContent *aContent,
                                                       int32_t aContentOffset,
                                                       int32_t aContentLength,
                                                       bool aSlowCheck) const;

  /** SetDragState(bool);
   *  sets the drag state to aState for resons of drag state.
   * @param aState is the new state of drag
   */
  MOZ_CAN_RUN_SCRIPT_BOUNDARY
  void SetDragState(bool aState);

  /** GetDragState(bool *);
   *  gets the drag state to aState for resons of drag state.
   * @param aState will hold the state of drag
   */
  bool GetDragState() const { return mDragState; }

  /**
    if we are in table cell selection mode. aka ctrl click in table cell
   */
  bool GetTableCellSelection() const
  {
    return mSelectingTableCellMode != mozilla::TableSelection::None;
  }
  void ClearTableCellSelection()
  {
    mSelectingTableCellMode = mozilla::TableSelection::None;
  }

  /** GetSelection
   * no query interface for selection. must use this method now.
   * @param aSelectionType The selection type what you want.
   */
  mozilla::dom::Selection*
    GetSelection(mozilla::SelectionType aSelectionType) const;

  /**
   * ScrollSelectionIntoView scrolls a region of the selection,
   * so that it is visible in the scrolled view.
   *
   * @param aSelectionType the selection to scroll into view.
   * @param aRegion the region inside the selection to scroll into view.
   * @param aFlags the scroll flags.  Valid bits include:
   * SCROLL_SYNCHRONOUS: when set, scrolls the selection into view
   * before returning. If not set, posts a request which is processed
   * at some point after the method returns.
   * SCROLL_FIRST_ANCESTOR_ONLY: if set, only the first ancestor will be scrolled
   * into view.
   *
   */
  /*unsafe*/
  nsresult ScrollSelectionIntoView(mozilla::SelectionType aSelectionType,
                                   SelectionRegion aRegion,
                                   int16_t aFlags) const;

  /** RepaintSelection repaints the selected frames that are inside the selection
   *  specified by aSelectionType.
   * @param aSelectionType The selection type what you want to repaint.
   */
  nsresult RepaintSelection(mozilla::SelectionType aSelectionType);

  /** GetFrameForNodeOffset given a node and its child offset, return the nsIFrame and
   *  the offset into that frame.
   * @param aNode input parameter for the node to look at
   * @param aOffset offset into above node.
   * @param aReturnOffset will contain offset into frame.
   */
  nsIFrame* GetFrameForNodeOffset(nsIContent*        aNode,
                                  int32_t            aOffset,
                                  CaretAssociateHint aHint,
                                  int32_t*           aReturnOffset) const;

  /**
   * GetFrameToPageSelect() returns a frame which is ancestor limit of
   * per-page selection.  The frame may not be scrollable.  E.g.,
   * when selection ancestor limit is set to a frame of an editing host of
   * contenteditable element and it's not scrollable.
   */
  nsIFrame* GetFrameToPageSelect() const;

  /**
   * Scrolling then moving caret placement code in common to text areas and
   * content areas should be located in the implementer
   * This method will accept the following parameters and perform the scroll
   * and caret movement.  It remains for the caller to call the final
   * ScrollCaretIntoView if that called wants to be sure the caret is always
   * visible.
   *
   * @param aForward if true, scroll forward if not scroll backward
   * @param aExtend  if true, extend selection to the new point
   * @param aFrame   the frame to scroll or container of per-page selection.
   *                 if aExtend is true and selection may have ancestor limit,
   *                 should set result of GetFrameToPageSelect().
   */
  MOZ_CAN_RUN_SCRIPT
  void CommonPageMove(bool aForward,
                      bool aExtend,
                      nsIFrame* aFrame);

  void SetHint(CaretAssociateHint aHintRight) { mHint = aHintRight; }
  CaretAssociateHint GetHint() const { return mHint; }

  /**
   * SetCaretBidiLevel sets the caret bidi level.
   * @param aLevel the caret bidi level
   */
  void SetCaretBidiLevel(nsBidiLevel aLevel);

  /**
   * GetCaretBidiLevel gets the caret bidi level.
   */
  nsBidiLevel GetCaretBidiLevel() const;

  /**
   * UndefineCaretBidiLevel sets the caret bidi level to "undefined".
   */
  void UndefineCaretBidiLevel();

  /** PhysicalMove will generally be called from the nsiselectioncontroller implementations.
   *  the effect being the selection will move one unit 'aAmount' in the
   *  given aDirection.
   * @param aDirection  the direction to move the selection
   * @param aAmount     amount of movement (char/line; word/page; eol/doc)
   * @param aExtend     continue selection
   */
  /*unsafe*/
  nsresult PhysicalMove(int16_t aDirection, int16_t aAmount, bool aExtend);

  /** CharacterMove will generally be called from the nsiselectioncontroller implementations.
   *  the effect being the selection will move one character left or right.
   * @param aForward move forward in document.
   * @param aExtend continue selection
   */
  /*unsafe*/
  nsresult CharacterMove(bool aForward, bool aExtend);

  /** CharacterExtendForDelete extends the selection forward (logically) to
   * the next character cell, so that the selected cell can be deleted.
   */
  /*unsafe*/
  nsresult CharacterExtendForDelete();

  /** CharacterExtendForBackspace extends the selection backward (logically) to
   * the previous character cell, so that the selected cell can be deleted.
   */
  /*unsafe*/
  nsresult CharacterExtendForBackspace();

  /** WordMove will generally be called from the nsiselectioncontroller implementations.
   *  the effect being the selection will move one word left or right.
   * @param aForward move forward in document.
   * @param aExtend continue selection
   */
  /*unsafe*/
  nsresult WordMove(bool aForward, bool aExtend);

  /** WordExtendForDelete extends the selection backward or forward
   *  (logically) to the next word boundary, so that the selected word
   *  can be deleted.
   * @param aForward select forward in document.
   */
  /*unsafe*/
  nsresult WordExtendForDelete(bool aForward);

  /** LineMove will generally be called from the nsiselectioncontroller
   *  implementations.
   *  the effect being the selection will move one line up or down.
   * @param aForward move forward in document.
   * @param aExtend continue selection
   */
  /*unsafe*/
  nsresult LineMove(bool aForward, bool aExtend);

  /** IntraLineMove will generally be called from the nsiselectioncontroller
   *  implementations.
   *  the effect being the selection will move to beginning or end of line
   * @param aForward move forward in document.
   * @param aExtend continue selection
   */
  /*unsafe*/
  nsresult IntraLineMove(bool aForward, bool aExtend);

  /** Select All will generally be called from the nsiselectioncontroller implementations.
   *  it will select the whole doc
   */
  MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult SelectAll();

  /** Sets/Gets The display selection enum.
   */
  void SetDisplaySelection(int16_t aState) { mDisplaySelection = aState; }
  int16_t GetDisplaySelection() const { return mDisplaySelection; }

  /** This method can be used to store the data received during a MouseDown
   *  event so that we can place the caret during the MouseUp event.
   * @aMouseEvent the event received by the selection MouseDown
   *  handling method. A nullptr value can be use to tell this method
   *  that any data is storing is no longer valid.
   */
  void SetDelayedCaretData(mozilla::WidgetMouseEvent* aMouseEvent);

  /** Get the delayed MouseDown event data necessary to place the
   *  caret during MouseUp processing.
   * @return a pointer to the event received
   *  by the selection during MouseDown processing. It can be nullptr
   *  if the data is no longer valid.
   */
  bool HasDelayedCaretData() { return mDelayedMouseEventValid; }
  bool IsShiftDownInDelayedCaretData()
  {
    NS_ASSERTION(mDelayedMouseEventValid, "No valid delayed caret data");
    return mDelayedMouseEventIsShift;
  }
  uint32_t GetClickCountInDelayedCaretData()
  {
    NS_ASSERTION(mDelayedMouseEventValid, "No valid delayed caret data");
    return mDelayedMouseEventClickCount;
  }

  bool MouseDownRecorded()
  {
    return !GetDragState() &&
           HasDelayedCaretData() &&
           GetClickCountInDelayedCaretData() < 2;
  }

  /** Get the content node that limits the selection
   *  When searching up a nodes for parents, as in a text edit field
   *    in an browser page, we must stop at this node else we reach into the
   *    parent page, which is very bad!
   */
  nsIContent* GetLimiter() const { return mLimiter; }

  nsIContent* GetAncestorLimiter() const { return mAncestorLimiter; }
  MOZ_CAN_RUN_SCRIPT_BOUNDARY void SetAncestorLimiter(nsIContent* aLimiter);

  /** This will tell the frame selection that a double click has been pressed
   *  so it can track abort future drags if inside the same selection
   *  @aDoubleDown has the double click down happened
   */
  void SetMouseDoubleDown(bool aDoubleDown) { mMouseDoubleDownState = aDoubleDown; }

  /** This will return whether the double down flag was set.
   *  @return whether the double down flag was set
   */
  bool GetMouseDoubleDown() const { return mMouseDoubleDownState; }

  /**
   * GetPrevNextBidiLevels will return the frames and associated Bidi levels of the characters
   *   logically before and after a (collapsed) selection.
   *  @param aNode is the node containing the selection
   *  @param aContentOffset is the offset of the selection in the node
   *  @param aJumpLines If true, look across line boundaries.
   *                    If false, behave as if there were base-level frames at line edges.
   *
   *  @return A struct holding the before/after frame and the before/after level.
   *
   *  At the beginning and end of each line there is assumed to be a frame with
   *   Bidi level equal to the paragraph embedding level.
   *  In these cases the before frame and after frame respectively will be
   *   nullptr.
   */
  nsPrevNextBidiLevels GetPrevNextBidiLevels(nsIContent* aNode,
                                             uint32_t aContentOffset,
                                             bool aJumpLines) const;

  /** GetFrameFromLevel will scan in a given direction
   *   until it finds a frame with a Bidi level less than or equal to a given level.
   *   It will return the last frame before this.
   *  @param aPresContext is the context to use
   *  @param aFrameIn is the frame to start from
   *  @param aDirection is the direction to scan
   *  @param aBidiLevel is the level to search for
   *  @param aFrameOut will hold the frame returned
   */
  nsresult GetFrameFromLevel(nsIFrame *aFrameIn,
                             nsDirection aDirection,
                             nsBidiLevel aBidiLevel,
                             nsIFrame **aFrameOut) const;

  /**
   * MaintainSelection will track the current selection as being "sticky".
   * Dragging or extending selection will never allow for a subset
   * (or the whole) of the maintained selection to become unselected.
   * Primary use: double click selecting then dragging on second click
   * @param aAmount the initial amount of text selected (word, line or paragraph).
   *                For "line", use eSelectBeginLine.
   */
  nsresult MaintainSelection(nsSelectionAmount aAmount = eSelectNoAmount);

  nsresult ConstrainFrameAndPointToAnchorSubtree(nsIFrame* aFrame,
                                                 const nsPoint& aPoint,
                                                 nsIFrame** aRetFrame,
                                                 nsPoint& aRetPoint);

  nsFrameSelection();

  void StartBatchChanges();

  MOZ_CAN_RUN_SCRIPT_BOUNDARY
  void EndBatchChanges(int16_t aReason = nsISelectionListener::NO_REASON);

  /*unsafe*/
  nsresult DeleteFromDocument();

  nsIPresShell *GetShell()const  { return mShell; }

  void DisconnectFromPresShell();
  nsresult ClearNormalSelection();

private:
  ~nsFrameSelection();

  MOZ_CAN_RUN_SCRIPT
  nsresult TakeFocus(nsIContent *aNewFocus,
                     uint32_t aContentOffset,
                     uint32_t aContentEndOffset,
                     CaretAssociateHint aHint,
                     bool aContinueSelection,
                     bool aMultipleSelection);

  void BidiLevelFromMove(nsIPresShell* aPresShell,
                         nsIContent *aNode,
                         uint32_t aContentOffset,
                         nsSelectionAmount aAmount,
                         CaretAssociateHint aHint);
  void BidiLevelFromClick(nsIContent *aNewFocus, uint32_t aContentOffset);
  nsPrevNextBidiLevels GetPrevNextBidiLevels(nsIContent *aNode,
                                             uint32_t aContentOffset,
                                             CaretAssociateHint aHint,
                                             bool aJumpLines) const;

  bool AdjustForMaintainedSelection(nsIContent *aContent, int32_t aOffset);

// post and pop reasons for notifications. we may stack these later
  void    PostReason(int16_t aReason) { mSelectionChangeReason = aReason; }
  int16_t PopReason()
  {
    int16_t retval = mSelectionChangeReason;
    mSelectionChangeReason = nsISelectionListener::NO_REASON;
    return retval;
  }
  bool IsUserSelectionReason() const
  {
    return (mSelectionChangeReason &
            (nsISelectionListener::DRAG_REASON |
             nsISelectionListener::MOUSEDOWN_REASON |
             nsISelectionListener::MOUSEUP_REASON |
             nsISelectionListener::KEYPRESS_REASON)) !=
           nsISelectionListener::NO_REASON;
  }

  friend class mozilla::dom::Selection;
  friend class mozilla::SelectionChangeEventDispatcher;
  friend struct mozilla::AutoPrepareFocusRange;
#ifdef DEBUG
  void printSelection();       // for debugging
#endif /* DEBUG */

  void ResizeBuffer(uint32_t aNewBufSize);

/*HELPER METHODS*/
  // Whether MoveCaret should use logical or visual movement,
  // or follow the bidi.edit.caret_movement_style preference.
  enum CaretMovementStyle {
    eLogical,
    eVisual,
    eUsePrefStyle
  };
  MOZ_CAN_RUN_SCRIPT_BOUNDARY
  nsresult     MoveCaret(nsDirection aDirection, bool aContinueSelection,
                         nsSelectionAmount aAmount,
                         CaretMovementStyle aMovementStyle);

  nsresult     FetchDesiredPos(nsPoint &aDesiredPos); //the position requested by the Key Handling for up down
  void         InvalidateDesiredPos(); //do not listen to mDesiredPos you must get another.
  void         SetDesiredPos(nsPoint aPos); //set the mDesiredPos

  uint32_t     GetBatching() const {return mBatching; }
  bool         GetNotifyFrames() const { return mNotifyFrames; }
  void         SetDirty(bool aDirty=true){if (mBatching) mChangesDuringBatching = aDirty;}

  // nsFrameSelection may get deleted when calling this,
  // so remember to use nsCOMPtr when needed.
  MOZ_CAN_RUN_SCRIPT
  nsresult     NotifySelectionListeners(mozilla::SelectionType aSelectionType);
  // Update the selection cache on repaint when the
  // selection being repainted is not empty.
  nsresult     UpdateSelectionCacheOnRepaintSelection(mozilla::dom::
                                                      Selection* aSel);

  // Table selection support.
  nsITableCellLayout* GetCellLayout(nsIContent *aCellContent) const;

  nsresult SelectBlockOfCells(nsIContent *aStartNode, nsIContent *aEndNode);
  nsresult SelectRowOrColumn(nsIContent *aCellContent,
                             mozilla::TableSelection aTarget);
  nsresult UnselectCells(nsIContent *aTable,
                         int32_t aStartRowIndex, int32_t aStartColumnIndex,
                         int32_t aEndRowIndex, int32_t aEndColumnIndex,
                         bool aRemoveOutsideOfCellRange);

  nsresult GetCellIndexes(nsIContent *aCell, int32_t &aRowIndex, int32_t &aColIndex);

  // Get our first range, if its first selected node is a cell.  If this does
  // not return null, then the first node in the returned range is a cell
  // (according to GetFirstCellNodeInRange).
  nsRange* GetFirstCellRange();
  // Get our next range, if its first selected node is a cell.  If this does
  // not return null, then the first node in the returned range is a cell
  // (according to GetFirstCellNodeInRange).
  nsRange* GetNextCellRange();
  nsIContent* GetFirstCellNodeInRange(nsRange *aRange) const;
  // Returns non-null table if in same table, null otherwise
  nsIContent* IsInSameTable(nsIContent *aContent1, nsIContent *aContent2) const;
  // Might return null
  nsIContent* GetParentTable(nsIContent *aCellNode) const;
  nsresult CreateAndAddRange(nsINode* aContainer, int32_t aOffset);

  ////////////BEGIN nsFrameSelection members

  RefPtr<mozilla::dom::Selection>
    mDomSelections[
      sizeof(mozilla::kPresentSelectionTypes) / sizeof(mozilla::SelectionType)];

  nsCOMPtr<nsINode> mCellParent; //used to snap to table selection
  nsCOMPtr<nsIContent> mStartSelectedCell;
  nsCOMPtr<nsIContent> mEndSelectedCell;
  nsCOMPtr<nsIContent> mAppendStartSelectedCell;
  nsCOMPtr<nsIContent> mUnselectCellOnMouseUp;
  mozilla::TableSelection mSelectingTableCellMode = mozilla::TableSelection::None;
  int32_t  mSelectedCellIndex = 0;

  // maintain selection
  RefPtr<nsRange> mMaintainRange;
  nsSelectionAmount mMaintainedAmount = eSelectNoAmount;

  //batching
  int32_t mBatching = 0;

  // Limit selection navigation to a child of this node.
  nsCOMPtr<nsIContent> mLimiter;
  // Limit selection navigation to a descendant of this node.
  nsCOMPtr<nsIContent> mAncestorLimiter;

  nsIPresShell* mShell = nullptr;
  // Reason for notifications of selection changing.
  int16_t mSelectionChangeReason = nsISelectionListener::NO_REASON;
  // For visual display purposes.
  int16_t mDisplaySelection = nsISelectionController::SELECTION_OFF;

  // Hint to tell if the selection is at the end of this line or beginning of next.
  CaretAssociateHint mHint = mozilla::CARET_ASSOCIATE_BEFORE;
  nsBidiLevel mCaretBidiLevel = BIDI_LEVEL_UNDEFINED;
  nsBidiLevel mKbdBidiLevel = NSBIDI_LTR;

  nsPoint mDesiredPos;
  bool mDelayedMouseEventValid = false;
  // These values are not used since they are only valid when
  // mDelayedMouseEventValid is true, and setting mDelayedMouseEventValid
  // always overrides these values.
  uint32_t mDelayedMouseEventClickCount = 0;
  bool mDelayedMouseEventIsShift = false;

  bool mChangesDuringBatching = false;
  bool mNotifyFrames = true;
  bool mDragSelectingCells = false;
  bool mDragState = false;   //for drag purposes
  bool mMouseDoubleDownState = false; //has the doubleclick down happened
  bool mDesiredPosSet = false;
  bool mAccessibleCaretEnabled = false;

  int8_t mCaretMovementStyle = 0;

  static bool sSelectionEventsEnabled;
  static bool sSelectionEventsOnTextControlsEnabled;
};

#endif /* nsFrameSelection_h___ */