Home | History | Annotate | Download | only in page
      1 /*
      2  * Copyright (C) 2007, 2009 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  *
      8  * 1.  Redistributions of source code must retain the above copyright
      9  *     notice, this list of conditions and the following disclaimer.
     10  * 2.  Redistributions in binary form must reproduce the above copyright
     11  *     notice, this list of conditions and the following disclaimer in the
     12  *     documentation and/or other materials provided with the distribution.
     13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     14  *     its contributors may be used to endorse or promote products derived
     15  *     from this software without specific prior written permission.
     16  *
     17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 
     30 #include "config.h"
     31 #include "DOMSelection.h"
     32 
     33 #include "ExceptionCode.h"
     34 #include "Frame.h"
     35 #include "Node.h"
     36 #include "PlatformString.h"
     37 #include "Range.h"
     38 #include "SelectionController.h"
     39 #include "TextIterator.h"
     40 #include "htmlediting.h"
     41 
     42 namespace WebCore {
     43 
     44 static Node* selectionShadowAncestor(Frame* frame)
     45 {
     46     Node* node = frame->selection()->selection().base().anchorNode();
     47     if (!node)
     48         return 0;
     49     Node* shadowAncestor = node->shadowAncestorNode();
     50     if (shadowAncestor == node)
     51         return 0;
     52     return shadowAncestor;
     53 }
     54 
     55 DOMSelection::DOMSelection(Frame* frame)
     56     : m_frame(frame)
     57 {
     58 }
     59 
     60 Frame* DOMSelection::frame() const
     61 {
     62     return m_frame;
     63 }
     64 
     65 void DOMSelection::disconnectFrame()
     66 {
     67     m_frame = 0;
     68 }
     69 
     70 const VisibleSelection& DOMSelection::visibleSelection() const
     71 {
     72     ASSERT(m_frame);
     73     return m_frame->selection()->selection();
     74 }
     75 
     76 static Position anchorPosition(const VisibleSelection& selection)
     77 {
     78     Position anchor = selection.isBaseFirst() ? selection.start() : selection.end();
     79     return anchor.parentAnchoredEquivalent();
     80 }
     81 
     82 static Position focusPosition(const VisibleSelection& selection)
     83 {
     84     Position focus = selection.isBaseFirst() ? selection.end() : selection.start();
     85     return focus.parentAnchoredEquivalent();
     86 }
     87 
     88 static Position basePosition(const VisibleSelection& selection)
     89 {
     90     return selection.base().parentAnchoredEquivalent();
     91 }
     92 
     93 static Position extentPosition(const VisibleSelection& selection)
     94 {
     95     return selection.extent().parentAnchoredEquivalent();
     96 }
     97 
     98 Node* DOMSelection::anchorNode() const
     99 {
    100     if (!m_frame)
    101         return 0;
    102     if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
    103         return shadowAncestor->parentNodeGuaranteedHostFree();
    104     return anchorPosition(visibleSelection()).containerNode();
    105 }
    106 
    107 int DOMSelection::anchorOffset() const
    108 {
    109     if (!m_frame)
    110         return 0;
    111     if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
    112         return shadowAncestor->nodeIndex();
    113     return anchorPosition(visibleSelection()).offsetInContainerNode();
    114 }
    115 
    116 Node* DOMSelection::focusNode() const
    117 {
    118     if (!m_frame)
    119         return 0;
    120     if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
    121         return shadowAncestor->parentNodeGuaranteedHostFree();
    122     return focusPosition(visibleSelection()).containerNode();
    123 }
    124 
    125 int DOMSelection::focusOffset() const
    126 {
    127     if (!m_frame)
    128         return 0;
    129     if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
    130         return shadowAncestor->nodeIndex();
    131     return focusPosition(visibleSelection()).offsetInContainerNode();
    132 }
    133 
    134 Node* DOMSelection::baseNode() const
    135 {
    136     if (!m_frame)
    137         return 0;
    138     if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
    139         return shadowAncestor->parentNodeGuaranteedHostFree();
    140     return basePosition(visibleSelection()).containerNode();
    141 }
    142 
    143 int DOMSelection::baseOffset() const
    144 {
    145     if (!m_frame)
    146         return 0;
    147     if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
    148         return shadowAncestor->nodeIndex();
    149     return basePosition(visibleSelection()).offsetInContainerNode();
    150 }
    151 
    152 Node* DOMSelection::extentNode() const
    153 {
    154     if (!m_frame)
    155         return 0;
    156     if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
    157         return shadowAncestor->parentNodeGuaranteedHostFree();
    158     return extentPosition(visibleSelection()).containerNode();
    159 }
    160 
    161 int DOMSelection::extentOffset() const
    162 {
    163     if (!m_frame)
    164         return 0;
    165     if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
    166         return shadowAncestor->nodeIndex();
    167     return extentPosition(visibleSelection()).offsetInContainerNode();
    168 }
    169 
    170 bool DOMSelection::isCollapsed() const
    171 {
    172     if (!m_frame || selectionShadowAncestor(m_frame))
    173         return true;
    174     return !m_frame->selection()->isRange();
    175 }
    176 
    177 String DOMSelection::type() const
    178 {
    179     if (!m_frame)
    180         return String();
    181 
    182     SelectionController* selection = m_frame->selection();
    183 
    184     // This is a WebKit DOM extension, incompatible with an IE extension
    185     // IE has this same attribute, but returns "none", "text" and "control"
    186     // http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx
    187     if (selection->isNone())
    188         return "None";
    189     if (selection->isCaret())
    190         return "Caret";
    191     return "Range";
    192 }
    193 
    194 int DOMSelection::rangeCount() const
    195 {
    196     if (!m_frame)
    197         return 0;
    198     return m_frame->selection()->isNone() ? 0 : 1;
    199 }
    200 
    201 void DOMSelection::collapse(Node* node, int offset, ExceptionCode& ec)
    202 {
    203     if (!m_frame)
    204         return;
    205 
    206     if (offset < 0) {
    207         ec = INDEX_SIZE_ERR;
    208         return;
    209     }
    210 
    211     if (!isValidForPosition(node))
    212         return;
    213 
    214     // FIXME: Eliminate legacy editing positions
    215     m_frame->selection()->moveTo(VisiblePosition(Position(node, offset), DOWNSTREAM));
    216 }
    217 
    218 void DOMSelection::collapseToEnd(ExceptionCode& ec)
    219 {
    220     if (!m_frame)
    221         return;
    222 
    223     const VisibleSelection& selection = m_frame->selection()->selection();
    224 
    225     if (selection.isNone()) {
    226         ec = INVALID_STATE_ERR;
    227         return;
    228     }
    229 
    230     m_frame->selection()->moveTo(VisiblePosition(selection.end(), DOWNSTREAM));
    231 }
    232 
    233 void DOMSelection::collapseToStart(ExceptionCode& ec)
    234 {
    235     if (!m_frame)
    236         return;
    237 
    238     const VisibleSelection& selection = m_frame->selection()->selection();
    239 
    240     if (selection.isNone()) {
    241         ec = INVALID_STATE_ERR;
    242         return;
    243     }
    244 
    245     m_frame->selection()->moveTo(VisiblePosition(selection.start(), DOWNSTREAM));
    246 }
    247 
    248 void DOMSelection::empty()
    249 {
    250     if (!m_frame)
    251         return;
    252     m_frame->selection()->clear();
    253 }
    254 
    255 void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionCode& ec)
    256 {
    257     if (!m_frame)
    258         return;
    259 
    260     if (baseOffset < 0 || extentOffset < 0) {
    261         ec = INDEX_SIZE_ERR;
    262         return;
    263     }
    264 
    265     if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode))
    266         return;
    267 
    268     // FIXME: Eliminate legacy editing positions
    269     VisiblePosition visibleBase = VisiblePosition(Position(baseNode, baseOffset), DOWNSTREAM);
    270     VisiblePosition visibleExtent = VisiblePosition(Position(extentNode, extentOffset), DOWNSTREAM);
    271 
    272     m_frame->selection()->moveTo(visibleBase, visibleExtent);
    273 }
    274 
    275 void DOMSelection::setPosition(Node* node, int offset, ExceptionCode& ec)
    276 {
    277     if (!m_frame)
    278         return;
    279     if (offset < 0) {
    280         ec = INDEX_SIZE_ERR;
    281         return;
    282     }
    283 
    284     if (!isValidForPosition(node))
    285         return;
    286 
    287     // FIXME: Eliminate legacy editing positions
    288     m_frame->selection()->moveTo(VisiblePosition(Position(node, offset), DOWNSTREAM));
    289 }
    290 
    291 void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString)
    292 {
    293     if (!m_frame)
    294         return;
    295 
    296     SelectionController::EAlteration alter;
    297     if (equalIgnoringCase(alterString, "extend"))
    298         alter = SelectionController::AlterationExtend;
    299     else if (equalIgnoringCase(alterString, "move"))
    300         alter = SelectionController::AlterationMove;
    301     else
    302         return;
    303 
    304     SelectionDirection direction;
    305     if (equalIgnoringCase(directionString, "forward"))
    306         direction = DirectionForward;
    307     else if (equalIgnoringCase(directionString, "backward"))
    308         direction = DirectionBackward;
    309     else if (equalIgnoringCase(directionString, "left"))
    310         direction = DirectionLeft;
    311     else if (equalIgnoringCase(directionString, "right"))
    312         direction = DirectionRight;
    313     else
    314         return;
    315 
    316     TextGranularity granularity;
    317     if (equalIgnoringCase(granularityString, "character"))
    318         granularity = CharacterGranularity;
    319     else if (equalIgnoringCase(granularityString, "word"))
    320         granularity = WordGranularity;
    321     else if (equalIgnoringCase(granularityString, "sentence"))
    322         granularity = SentenceGranularity;
    323     else if (equalIgnoringCase(granularityString, "line"))
    324         granularity = LineGranularity;
    325     else if (equalIgnoringCase(granularityString, "paragraph"))
    326         granularity = ParagraphGranularity;
    327     else if (equalIgnoringCase(granularityString, "lineboundary"))
    328         granularity = LineBoundary;
    329     else if (equalIgnoringCase(granularityString, "sentenceboundary"))
    330         granularity = SentenceBoundary;
    331     else if (equalIgnoringCase(granularityString, "paragraphboundary"))
    332         granularity = ParagraphBoundary;
    333     else if (equalIgnoringCase(granularityString, "documentboundary"))
    334         granularity = DocumentBoundary;
    335     else if (equalIgnoringCase(granularityString, "-webkit-visual-word"))
    336         granularity = WebKitVisualWordGranularity;
    337     else
    338         return;
    339 
    340     m_frame->selection()->modify(alter, direction, granularity, false);
    341 }
    342 
    343 void DOMSelection::extend(Node* node, int offset, ExceptionCode& ec)
    344 {
    345     if (!m_frame)
    346         return;
    347 
    348     if (!node) {
    349         ec = TYPE_MISMATCH_ERR;
    350         return;
    351     }
    352 
    353     if (offset < 0 || offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->childNodeCount())) {
    354         ec = INDEX_SIZE_ERR;
    355         return;
    356     }
    357 
    358     if (!isValidForPosition(node))
    359         return;
    360 
    361     // FIXME: Eliminate legacy editing positions
    362     m_frame->selection()->setExtent(VisiblePosition(Position(node, offset), DOWNSTREAM));
    363 }
    364 
    365 PassRefPtr<Range> DOMSelection::getRangeAt(int index, ExceptionCode& ec)
    366 {
    367     if (!m_frame)
    368         return 0;
    369 
    370     if (index < 0 || index >= rangeCount()) {
    371         ec = INDEX_SIZE_ERR;
    372         return 0;
    373     }
    374 
    375     // If you're hitting this, you've added broken multi-range selection support
    376     ASSERT(rangeCount() == 1);
    377 
    378     if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) {
    379         ContainerNode* container = shadowAncestor->parentNodeGuaranteedHostFree();
    380         int offset = shadowAncestor->nodeIndex();
    381         return Range::create(shadowAncestor->document(), container, offset, container, offset);
    382     }
    383 
    384     const VisibleSelection& selection = m_frame->selection()->selection();
    385     return selection.firstRange();
    386 }
    387 
    388 void DOMSelection::removeAllRanges()
    389 {
    390     if (!m_frame)
    391         return;
    392     m_frame->selection()->clear();
    393 }
    394 
    395 void DOMSelection::addRange(Range* r)
    396 {
    397     if (!m_frame)
    398         return;
    399     if (!r)
    400         return;
    401 
    402     SelectionController* selection = m_frame->selection();
    403 
    404     if (selection->isNone()) {
    405         selection->setSelection(VisibleSelection(r));
    406         return;
    407     }
    408 
    409     RefPtr<Range> range = selection->selection().toNormalizedRange();
    410     ExceptionCode ec = 0;
    411     if (r->compareBoundaryPoints(Range::START_TO_START, range.get(), ec) == -1) {
    412         // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
    413         if (r->compareBoundaryPoints(Range::START_TO_END, range.get(), ec) > -1) {
    414             if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1)
    415                 // The original range and r intersect.
    416                 selection->setSelection(VisibleSelection(r->startPosition(), range->endPosition(), DOWNSTREAM));
    417             else
    418                 // r contains the original range.
    419                 selection->setSelection(VisibleSelection(r));
    420         }
    421     } else {
    422         // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
    423         if (r->compareBoundaryPoints(Range::END_TO_START, range.get(), ec) < 1) {
    424             if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1)
    425                 // The original range contains r.
    426                 selection->setSelection(VisibleSelection(range.get()));
    427             else
    428                 // The original range and r intersect.
    429                 selection->setSelection(VisibleSelection(range->startPosition(), r->endPosition(), DOWNSTREAM));
    430         }
    431     }
    432 }
    433 
    434 void DOMSelection::deleteFromDocument()
    435 {
    436     if (!m_frame)
    437         return;
    438 
    439     SelectionController* selection = m_frame->selection();
    440 
    441     if (selection->isNone())
    442         return;
    443 
    444     if (isCollapsed())
    445         selection->modify(SelectionController::AlterationExtend, DirectionBackward, CharacterGranularity);
    446 
    447     RefPtr<Range> selectedRange = selection->selection().toNormalizedRange();
    448     if (!selectedRange)
    449         return;
    450 
    451     ExceptionCode ec = 0;
    452     selectedRange->deleteContents(ec);
    453     ASSERT(!ec);
    454 
    455     setBaseAndExtent(selectedRange->startContainer(ec), selectedRange->startOffset(ec), selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec);
    456     ASSERT(!ec);
    457 }
    458 
    459 bool DOMSelection::containsNode(const Node* n, bool allowPartial) const
    460 {
    461     if (!m_frame)
    462         return false;
    463 
    464     SelectionController* selection = m_frame->selection();
    465 
    466     if (!n || m_frame->document() != n->document() || selection->isNone())
    467         return false;
    468 
    469     ContainerNode* parentNode = n->parentNode();
    470     unsigned nodeIndex = n->nodeIndex();
    471     RefPtr<Range> selectedRange = selection->selection().toNormalizedRange();
    472 
    473     if (!parentNode)
    474         return false;
    475 
    476     ExceptionCode ec = 0;
    477     bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec) >= 0 && !ec
    478         && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(ec), selectedRange->endOffset(ec), ec) <= 0 && !ec;
    479     ASSERT(!ec);
    480     if (nodeFullySelected)
    481         return true;
    482 
    483     bool nodeFullyUnselected = (Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->endContainer(ec), selectedRange->endOffset(ec), ec) > 0 && !ec)
    484         || (Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec) < 0 && !ec);
    485     ASSERT(!ec);
    486     if (nodeFullyUnselected)
    487         return false;
    488 
    489     return allowPartial || n->isTextNode();
    490 }
    491 
    492 void DOMSelection::selectAllChildren(Node* n, ExceptionCode& ec)
    493 {
    494     if (!n)
    495         return;
    496 
    497     // This doesn't (and shouldn't) select text node characters.
    498     setBaseAndExtent(n, 0, n, n->childNodeCount(), ec);
    499 }
    500 
    501 String DOMSelection::toString()
    502 {
    503     if (!m_frame)
    504         return String();
    505 
    506     return plainText(m_frame->selection()->selection().toNormalizedRange().get());
    507 }
    508 
    509 bool DOMSelection::isValidForPosition(Node* node) const
    510 {
    511     ASSERT(m_frame);
    512     if (!node)
    513         return true;
    514     return node->document() == m_frame->document();
    515 }
    516 
    517 } // namespace WebCore
    518