Home | History | Annotate | Download | only in dom
      1 /*
      2  * Copyright (C) 2004, 2006, 2008 Apple Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #ifndef Position_h
     27 #define Position_h
     28 
     29 #include "TextAffinity.h"
     30 #include "TextDirection.h"
     31 #include "Node.h" // for position creation functions
     32 #include <wtf/Assertions.h>
     33 #include <wtf/PassRefPtr.h>
     34 #include <wtf/RefPtr.h>
     35 
     36 namespace WebCore {
     37 
     38 class CSSComputedStyleDeclaration;
     39 class Element;
     40 class InlineBox;
     41 class Node;
     42 class Range;
     43 class RenderObject;
     44 
     45 enum PositionMoveType {
     46     CodePoint,       // Move by a single code point.
     47     Character,       // Move to the next Unicode character break.
     48     BackwardDeletion // Subject to platform conventions.
     49 };
     50 
     51 class Position {
     52 public:
     53     enum AnchorType {
     54         PositionIsOffsetInAnchor,
     55         PositionIsAfterAnchor,
     56         PositionIsBeforeAnchor
     57     };
     58 
     59     enum EditingBoundaryCrossingRule {
     60         CanCrossEditingBoundary,
     61         CannotCrossEditingBoundary
     62     };
     63 
     64     Position()
     65         : m_offset(0)
     66         , m_anchorType(PositionIsOffsetInAnchor)
     67         , m_isLegacyEditingPosition(false)
     68     {
     69     }
     70 
     71     // For creating legacy editing positions: (Anchor type will be determined from editingIgnoresContent(node))
     72     Position(PassRefPtr<Node> anchorNode, int offset);
     73 
     74     // For creating before/after positions:
     75     Position(PassRefPtr<Node> anchorNode, AnchorType);
     76     // For creating offset positions:
     77     Position(PassRefPtr<Node> anchorNode, int offset, AnchorType);
     78 
     79     AnchorType anchorType() const { return static_cast<AnchorType>(m_anchorType); }
     80 
     81     void clear() { m_anchorNode.clear(); m_offset = 0; m_anchorType = PositionIsOffsetInAnchor; m_isLegacyEditingPosition = false; }
     82 
     83     // These are always DOM compliant values.  Editing positions like [img, 0] (aka [img, before])
     84     // will return img->parentNode() and img->nodeIndex() from these functions.
     85     Node* containerNode() const; // NULL for a before/after position anchored to a node with no parent
     86     int computeOffsetInContainerNode() const;  // O(n) for before/after-anchored positions, O(1) for parent-anchored positions
     87 
     88     // Inline O(1) access for Positions which callers know to be parent-anchored
     89     int offsetInContainerNode() const
     90     {
     91         ASSERT(anchorType() == PositionIsOffsetInAnchor);
     92         return m_offset;
     93     }
     94 
     95     // New code should not use this function.
     96     int deprecatedEditingOffset() const
     97     {
     98         // This should probably ASSERT(m_isLegacyEditingPosition);
     99         return m_offset;
    100     }
    101 
    102     // These are convenience methods which are smart about whether the position is neighbor anchored or parent anchored
    103     Node* computeNodeBeforePosition() const;
    104     Node* computeNodeAfterPosition() const;
    105 
    106     Node* anchorNode() const { return m_anchorNode.get(); }
    107 
    108     // FIXME: Callers should be moved off of node(), node() is not always the container for this position.
    109     // For nodes which editingIgnoresContent(node()) returns true, positions like [ignoredNode, 0]
    110     // will be treated as before ignoredNode (thus node() is really after the position, not containing it).
    111     Node* node() const { return m_anchorNode.get(); }
    112 
    113     // These should only be used for PositionIsOffsetInAnchor positions, unless
    114     // the position is a legacy editing position.
    115     void moveToPosition(PassRefPtr<Node> anchorNode, int offset);
    116     void moveToOffset(int offset);
    117 
    118     bool isNull() const { return !m_anchorNode; }
    119     bool isNotNull() const { return m_anchorNode; }
    120 
    121     Element* element() const;
    122     PassRefPtr<CSSComputedStyleDeclaration> computedStyle() const;
    123 
    124     // Move up or down the DOM by one position.
    125     // Offsets are computed using render text for nodes that have renderers - but note that even when
    126     // using composed characters, the result may be inside a single user-visible character if a ligature is formed.
    127     Position previous(PositionMoveType = CodePoint) const;
    128     Position next(PositionMoveType = CodePoint) const;
    129     static int uncheckedPreviousOffset(const Node*, int current);
    130     static int uncheckedPreviousOffsetForBackwardDeletion(const Node*, int current);
    131     static int uncheckedNextOffset(const Node*, int current);
    132 
    133     // These can be either inside or just before/after the node, depending on
    134     // if the node is ignored by editing or not.
    135     bool atFirstEditingPositionForNode() const;
    136     bool atLastEditingPositionForNode() const;
    137 
    138     // Returns true if the visually equivalent positions around have different editability
    139     bool atEditingBoundary() const;
    140 
    141     bool atStartOfTree() const;
    142     bool atEndOfTree() const;
    143 
    144     // FIXME: Make these non-member functions and put them somewhere in the editing directory.
    145     // These aren't really basic "position" operations. More high level editing helper functions.
    146     Position leadingWhitespacePosition(EAffinity, bool considerNonCollapsibleWhitespace = false) const;
    147     Position trailingWhitespacePosition(EAffinity, bool considerNonCollapsibleWhitespace = false) const;
    148 
    149     // These return useful visually equivalent positions.
    150     Position upstream(EditingBoundaryCrossingRule = CannotCrossEditingBoundary) const;
    151     Position downstream(EditingBoundaryCrossingRule = CannotCrossEditingBoundary) const;
    152 
    153     bool isCandidate() const;
    154     bool inRenderedText() const;
    155     bool isRenderedCharacter() const;
    156     bool rendersInDifferentPosition(const Position&) const;
    157 
    158     void getInlineBoxAndOffset(EAffinity, InlineBox*&, int& caretOffset) const;
    159     void getInlineBoxAndOffset(EAffinity, TextDirection primaryDirection, InlineBox*&, int& caretOffset) const;
    160 
    161     static bool hasRenderedNonAnonymousDescendantsWithHeight(RenderObject*);
    162     static bool nodeIsUserSelectNone(Node*);
    163 
    164     void debugPosition(const char* msg = "") const;
    165 
    166 #ifndef NDEBUG
    167     void formatForDebugger(char* buffer, unsigned length) const;
    168     void showTreeForThis() const;
    169 #endif
    170 
    171 private:
    172     int renderedOffset() const;
    173 
    174     Position previousCharacterPosition(EAffinity) const;
    175     Position nextCharacterPosition(EAffinity) const;
    176 
    177     static AnchorType anchorTypeForLegacyEditingPosition(Node* anchorNode, int offset);
    178 
    179     RefPtr<Node> m_anchorNode;
    180     // m_offset can be the offset inside m_anchorNode, or if editingIgnoresContent(m_anchorNode)
    181     // returns true, then other places in editing will treat m_offset == 0 as "before the anchor"
    182     // and m_offset > 0 as "after the anchor node".  See rangeCompliantEquivalent for more info.
    183     int m_offset;
    184     unsigned m_anchorType : 2;
    185     bool m_isLegacyEditingPosition : 1;
    186 };
    187 
    188 inline bool operator==(const Position& a, const Position& b)
    189 {
    190     // FIXME: In <div><img></div> [div, 0] != [img, 0] even though most of the
    191     // editing code will treat them as identical.
    192     return a.anchorNode() == b.anchorNode() && a.deprecatedEditingOffset() == b.deprecatedEditingOffset();
    193 }
    194 
    195 inline bool operator!=(const Position& a, const Position& b)
    196 {
    197     return !(a == b);
    198 }
    199 
    200 // We define position creation functions to make callsites more readable.
    201 // These are inline to prevent ref-churn when returning a Position object.
    202 // If we ever add a PassPosition we can make these non-inline.
    203 
    204 inline Position positionInParentBeforeNode(const Node* node)
    205 {
    206     // FIXME: This should ASSERT(node->parentNode())
    207     // At least one caller currently hits this ASSERT though, which indicates
    208     // that the caller is trying to make a position relative to a disconnected node (which is likely an error)
    209     // Specifically, editing/deleting/delete-ligature-001.html crashes with ASSERT(node->parentNode())
    210     return Position(node->parentNode(), node->nodeIndex(), Position::PositionIsOffsetInAnchor);
    211 }
    212 
    213 inline Position positionInParentAfterNode(const Node* node)
    214 {
    215     ASSERT(node->parentNode());
    216     return Position(node->parentNode(), node->nodeIndex() + 1, Position::PositionIsOffsetInAnchor);
    217 }
    218 
    219 // positionBeforeNode and positionAfterNode return neighbor-anchored positions, construction is O(1)
    220 inline Position positionBeforeNode(Node* anchorNode)
    221 {
    222     ASSERT(anchorNode);
    223     return Position(anchorNode, Position::PositionIsBeforeAnchor);
    224 }
    225 
    226 inline Position positionAfterNode(Node* anchorNode)
    227 {
    228     ASSERT(anchorNode);
    229     return Position(anchorNode, Position::PositionIsAfterAnchor);
    230 }
    231 
    232 inline int lastOffsetInNode(Node* node)
    233 {
    234     return node->offsetInCharacters() ? node->maxCharacterOffset() : node->childNodeCount();
    235 }
    236 
    237 // firstPositionInNode and lastPositionInNode return parent-anchored positions, lastPositionInNode construction is O(n) due to childNodeCount()
    238 inline Position firstPositionInNode(Node* anchorNode)
    239 {
    240     return Position(anchorNode, 0, Position::PositionIsOffsetInAnchor);
    241 }
    242 
    243 inline Position lastPositionInNode(Node* anchorNode)
    244 {
    245     return Position(anchorNode, lastOffsetInNode(anchorNode), Position::PositionIsOffsetInAnchor);
    246 }
    247 
    248 } // namespace WebCore
    249 
    250 #ifndef NDEBUG
    251 // Outside the WebCore namespace for ease of invocation from gdb.
    252 void showTree(const WebCore::Position&);
    253 void showTree(const WebCore::Position*);
    254 #endif
    255 
    256 #endif // Position_h
    257