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