Home | History | Annotate | Download | only in editing
      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/editing/DOMSelection.h"
     33 
     34 #include "bindings/core/v8/ExceptionMessages.h"
     35 #include "bindings/core/v8/ExceptionState.h"
     36 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
     37 #include "core/dom/Document.h"
     38 #include "core/dom/ExceptionCode.h"
     39 #include "core/dom/Node.h"
     40 #include "core/dom/Range.h"
     41 #include "core/dom/TreeScope.h"
     42 #include "core/editing/FrameSelection.h"
     43 #include "core/editing/TextIterator.h"
     44 #include "core/editing/htmlediting.h"
     45 #include "core/frame/LocalFrame.h"
     46 #include "core/inspector/ConsoleMessage.h"
     47 #include "wtf/text/WTFString.h"
     48 
     49 namespace blink {
     50 
     51 static Node* selectionShadowAncestor(LocalFrame* frame)
     52 {
     53     Node* node = frame->selection().selection().base().anchorNode();
     54     if (!node)
     55         return 0;
     56 
     57     if (!node->isInShadowTree())
     58         return 0;
     59 
     60     return frame->document()->ancestorInThisScope(node);
     61 }
     62 
     63 DOMSelection::DOMSelection(const TreeScope* treeScope)
     64     : DOMWindowProperty(treeScope->rootNode().document().frame())
     65     , m_treeScope(treeScope)
     66 {
     67 }
     68 
     69 void DOMSelection::clearTreeScope()
     70 {
     71     m_treeScope = nullptr;
     72 }
     73 
     74 const VisibleSelection& DOMSelection::visibleSelection() const
     75 {
     76     ASSERT(m_frame);
     77     return m_frame->selection().selection();
     78 }
     79 
     80 static Position anchorPosition(const VisibleSelection& selection)
     81 {
     82     Position anchor = selection.isBaseFirst() ? selection.start() : selection.end();
     83     return anchor.parentAnchoredEquivalent();
     84 }
     85 
     86 static Position focusPosition(const VisibleSelection& selection)
     87 {
     88     Position focus = selection.isBaseFirst() ? selection.end() : selection.start();
     89     return focus.parentAnchoredEquivalent();
     90 }
     91 
     92 static Position basePosition(const VisibleSelection& selection)
     93 {
     94     return selection.base().parentAnchoredEquivalent();
     95 }
     96 
     97 static Position extentPosition(const VisibleSelection& selection)
     98 {
     99     return selection.extent().parentAnchoredEquivalent();
    100 }
    101 
    102 Node* DOMSelection::anchorNode() const
    103 {
    104     if (!m_frame)
    105         return 0;
    106 
    107     return shadowAdjustedNode(anchorPosition(visibleSelection()));
    108 }
    109 
    110 int DOMSelection::anchorOffset() const
    111 {
    112     if (!m_frame)
    113         return 0;
    114 
    115     return shadowAdjustedOffset(anchorPosition(visibleSelection()));
    116 }
    117 
    118 Node* DOMSelection::focusNode() const
    119 {
    120     if (!m_frame)
    121         return 0;
    122 
    123     return shadowAdjustedNode(focusPosition(visibleSelection()));
    124 }
    125 
    126 int DOMSelection::focusOffset() const
    127 {
    128     if (!m_frame)
    129         return 0;
    130 
    131     return shadowAdjustedOffset(focusPosition(visibleSelection()));
    132 }
    133 
    134 Node* DOMSelection::baseNode() const
    135 {
    136     if (!m_frame)
    137         return 0;
    138 
    139     return shadowAdjustedNode(basePosition(visibleSelection()));
    140 }
    141 
    142 int DOMSelection::baseOffset() const
    143 {
    144     if (!m_frame)
    145         return 0;
    146 
    147     return shadowAdjustedOffset(basePosition(visibleSelection()));
    148 }
    149 
    150 Node* DOMSelection::extentNode() const
    151 {
    152     if (!m_frame)
    153         return 0;
    154 
    155     return shadowAdjustedNode(extentPosition(visibleSelection()));
    156 }
    157 
    158 int DOMSelection::extentOffset() const
    159 {
    160     if (!m_frame)
    161         return 0;
    162 
    163     return shadowAdjustedOffset(extentPosition(visibleSelection()));
    164 }
    165 
    166 bool DOMSelection::isCollapsed() const
    167 {
    168     if (!m_frame || selectionShadowAncestor(m_frame))
    169         return true;
    170     return !m_frame->selection().isRange();
    171 }
    172 
    173 String DOMSelection::type() const
    174 {
    175     if (!m_frame)
    176         return String();
    177 
    178     FrameSelection& selection = m_frame->selection();
    179 
    180     // This is a WebKit DOM extension, incompatible with an IE extension
    181     // IE has this same attribute, but returns "none", "text" and "control"
    182     // http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx
    183     if (selection.isNone())
    184         return "None";
    185     if (selection.isCaret())
    186         return "Caret";
    187     return "Range";
    188 }
    189 
    190 int DOMSelection::rangeCount() const
    191 {
    192     if (!m_frame)
    193         return 0;
    194     return m_frame->selection().isNone() ? 0 : 1;
    195 }
    196 
    197 void DOMSelection::collapse(Node* node, int offset, ExceptionState& exceptionState)
    198 {
    199     if (!m_frame)
    200         return;
    201 
    202     if (!node) {
    203         m_frame->selection().clear();
    204         return;
    205     }
    206 
    207     if (offset < 0) {
    208         exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is not a valid offset.");
    209         return;
    210     }
    211 
    212     if (!isValidForPosition(node))
    213         return;
    214     RefPtrWillBeRawPtr<Range> range = Range::create(node->document());
    215     range->setStart(node, offset, exceptionState);
    216     if (exceptionState.hadException())
    217         return;
    218     range->setEnd(node, offset, exceptionState);
    219     if (exceptionState.hadException())
    220         return;
    221     m_frame->selection().setSelectedRange(range.get(), DOWNSTREAM, m_frame->selection().isDirectional() ? FrameSelection::Directional : FrameSelection::NonDirectional);
    222 }
    223 
    224 void DOMSelection::collapseToEnd(ExceptionState& exceptionState)
    225 {
    226     if (!m_frame)
    227         return;
    228 
    229     const VisibleSelection& selection = m_frame->selection().selection();
    230 
    231     if (selection.isNone()) {
    232         exceptionState.throwDOMException(InvalidStateError, "there is no selection.");
    233         return;
    234     }
    235 
    236     m_frame->selection().moveTo(VisiblePosition(selection.end(), DOWNSTREAM));
    237 }
    238 
    239 void DOMSelection::collapseToStart(ExceptionState& exceptionState)
    240 {
    241     if (!m_frame)
    242         return;
    243 
    244     const VisibleSelection& selection = m_frame->selection().selection();
    245 
    246     if (selection.isNone()) {
    247         exceptionState.throwDOMException(InvalidStateError, "there is no selection.");
    248         return;
    249     }
    250 
    251     m_frame->selection().moveTo(VisiblePosition(selection.start(), DOWNSTREAM));
    252 }
    253 
    254 void DOMSelection::empty()
    255 {
    256     if (!m_frame)
    257         return;
    258     m_frame->selection().clear();
    259 }
    260 
    261 void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionState& exceptionState)
    262 {
    263     if (!m_frame)
    264         return;
    265 
    266     if (baseOffset < 0) {
    267         exceptionState.throwDOMException(IndexSizeError, String::number(baseOffset) + " is not a valid base offset.");
    268         return;
    269     }
    270 
    271     if (extentOffset < 0) {
    272         exceptionState.throwDOMException(IndexSizeError, String::number(extentOffset) + " is not a valid extent offset.");
    273         return;
    274     }
    275 
    276     if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode))
    277         return;
    278 
    279     // FIXME: Eliminate legacy editing positions
    280     VisiblePosition visibleBase = VisiblePosition(createLegacyEditingPosition(baseNode, baseOffset), DOWNSTREAM);
    281     VisiblePosition visibleExtent = VisiblePosition(createLegacyEditingPosition(extentNode, extentOffset), DOWNSTREAM);
    282 
    283     m_frame->selection().moveTo(visibleBase, visibleExtent);
    284 }
    285 
    286 void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString)
    287 {
    288     if (!m_frame)
    289         return;
    290 
    291     FrameSelection::EAlteration alter;
    292     if (equalIgnoringCase(alterString, "extend"))
    293         alter = FrameSelection::AlterationExtend;
    294     else if (equalIgnoringCase(alterString, "move"))
    295         alter = FrameSelection::AlterationMove;
    296     else
    297         return;
    298 
    299     SelectionDirection direction;
    300     if (equalIgnoringCase(directionString, "forward"))
    301         direction = DirectionForward;
    302     else if (equalIgnoringCase(directionString, "backward"))
    303         direction = DirectionBackward;
    304     else if (equalIgnoringCase(directionString, "left"))
    305         direction = DirectionLeft;
    306     else if (equalIgnoringCase(directionString, "right"))
    307         direction = DirectionRight;
    308     else
    309         return;
    310 
    311     TextGranularity granularity;
    312     if (equalIgnoringCase(granularityString, "character"))
    313         granularity = CharacterGranularity;
    314     else if (equalIgnoringCase(granularityString, "word"))
    315         granularity = WordGranularity;
    316     else if (equalIgnoringCase(granularityString, "sentence"))
    317         granularity = SentenceGranularity;
    318     else if (equalIgnoringCase(granularityString, "line"))
    319         granularity = LineGranularity;
    320     else if (equalIgnoringCase(granularityString, "paragraph"))
    321         granularity = ParagraphGranularity;
    322     else if (equalIgnoringCase(granularityString, "lineboundary"))
    323         granularity = LineBoundary;
    324     else if (equalIgnoringCase(granularityString, "sentenceboundary"))
    325         granularity = SentenceBoundary;
    326     else if (equalIgnoringCase(granularityString, "paragraphboundary"))
    327         granularity = ParagraphBoundary;
    328     else if (equalIgnoringCase(granularityString, "documentboundary"))
    329         granularity = DocumentBoundary;
    330     else
    331         return;
    332 
    333     m_frame->selection().modify(alter, direction, granularity);
    334 }
    335 
    336 void DOMSelection::extend(Node* node, int offset, ExceptionState& exceptionState)
    337 {
    338     ASSERT(node);
    339 
    340     if (!m_frame)
    341         return;
    342 
    343     if (offset < 0) {
    344         exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is not a valid offset.");
    345         return;
    346     }
    347     if (offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->countChildren())) {
    348         exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is larger than the given node's length.");
    349         return;
    350     }
    351 
    352     if (!isValidForPosition(node))
    353         return;
    354 
    355     // FIXME: Eliminate legacy editing positions
    356     m_frame->selection().setExtent(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
    357 }
    358 
    359 PassRefPtrWillBeRawPtr<Range> DOMSelection::getRangeAt(int index, ExceptionState& exceptionState)
    360 {
    361     if (!m_frame)
    362         return nullptr;
    363 
    364     if (index < 0 || index >= rangeCount()) {
    365         exceptionState.throwDOMException(IndexSizeError, String::number(index) + " is not a valid index.");
    366         return nullptr;
    367     }
    368 
    369     // If you're hitting this, you've added broken multi-range selection support
    370     ASSERT(rangeCount() == 1);
    371 
    372     if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) {
    373         ASSERT(!shadowAncestor->isShadowRoot());
    374         ContainerNode* container = shadowAncestor->parentOrShadowHostNode();
    375         int offset = shadowAncestor->nodeIndex();
    376         return Range::create(shadowAncestor->document(), container, offset, container, offset);
    377     }
    378 
    379     return m_frame->selection().firstRange();
    380 }
    381 
    382 void DOMSelection::removeAllRanges()
    383 {
    384     if (!m_frame)
    385         return;
    386     m_frame->selection().clear();
    387 }
    388 
    389 void DOMSelection::addRange(Range* newRange)
    390 {
    391     if (!m_frame)
    392         return;
    393 
    394     // FIXME: Should we throw DOMException for error cases below?
    395     if (!newRange) {
    396         addConsoleError("The given range is null.");
    397         return;
    398     }
    399 
    400     if (!newRange->startContainer()) {
    401         addConsoleError("The given range has no container. Perhaps 'detach()' has been invoked on it?");
    402         return;
    403     }
    404 
    405     FrameSelection& selection = m_frame->selection();
    406 
    407     if (selection.isNone()) {
    408         selection.setSelectedRange(newRange, VP_DEFAULT_AFFINITY);
    409         return;
    410     }
    411 
    412     RefPtrWillBeRawPtr<Range> originalRange = selection.firstRange();
    413 
    414     if (originalRange->startContainer()->document() != newRange->startContainer()->document()) {
    415         addConsoleError("The given range does not belong to the current selection's document.");
    416         return;
    417     }
    418     if (originalRange->startContainer()->treeScope() != newRange->startContainer()->treeScope()) {
    419         addConsoleError("The given range and the current selection belong to two different document fragments.");
    420         return;
    421     }
    422 
    423     if (originalRange->compareBoundaryPoints(Range::START_TO_END, newRange, ASSERT_NO_EXCEPTION) < 0
    424         || newRange->compareBoundaryPoints(Range::START_TO_END, originalRange.get(), ASSERT_NO_EXCEPTION) < 0) {
    425         addConsoleError("Discontiguous selection is not supported.");
    426         return;
    427     }
    428 
    429     // FIXME: "Merge the ranges if they intersect" is Blink-specific behavior; other browsers supporting discontiguous
    430     // selection (obviously) keep each Range added and return it in getRangeAt(). But it's unclear if we can really
    431     // do the same, since we don't support discontiguous selection. Further discussions at
    432     // <https://code.google.com/p/chromium/issues/detail?id=353069>.
    433 
    434     Range* start = originalRange->compareBoundaryPoints(Range::START_TO_START, newRange, ASSERT_NO_EXCEPTION) < 0 ? originalRange.get() : newRange;
    435     Range* end = originalRange->compareBoundaryPoints(Range::END_TO_END, newRange, ASSERT_NO_EXCEPTION) < 0 ? newRange : originalRange.get();
    436     RefPtrWillBeRawPtr<Range> merged = Range::create(originalRange->startContainer()->document(), start->startContainer(), start->startOffset(), end->endContainer(), end->endOffset());
    437     EAffinity affinity = selection.selection().affinity();
    438     selection.setSelectedRange(merged.get(), affinity);
    439 }
    440 
    441 void DOMSelection::deleteFromDocument()
    442 {
    443     if (!m_frame)
    444         return;
    445 
    446     FrameSelection& selection = m_frame->selection();
    447 
    448     if (selection.isNone())
    449         return;
    450 
    451     RefPtrWillBeRawPtr<Range> selectedRange = selection.selection().toNormalizedRange();
    452     if (!selectedRange)
    453         return;
    454 
    455     selectedRange->deleteContents(ASSERT_NO_EXCEPTION);
    456 
    457     setBaseAndExtent(selectedRange->startContainer(), selectedRange->startOffset(), selectedRange->startContainer(), selectedRange->startOffset(), ASSERT_NO_EXCEPTION);
    458 }
    459 
    460 bool DOMSelection::containsNode(const Node* n, bool allowPartial) const
    461 {
    462     if (!m_frame)
    463         return false;
    464 
    465     FrameSelection& selection = m_frame->selection();
    466 
    467     if (!n || m_frame->document() != n->document() || selection.isNone())
    468         return false;
    469 
    470     unsigned nodeIndex = n->nodeIndex();
    471     RefPtrWillBeRawPtr<Range> selectedRange = selection.selection().toNormalizedRange();
    472 
    473     ContainerNode* parentNode = n->parentNode();
    474     if (!parentNode)
    475         return false;
    476 
    477     TrackExceptionState exceptionState;
    478     bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(), selectedRange->startOffset(), exceptionState) >= 0 && !exceptionState.hadException()
    479         && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(), selectedRange->endOffset(), exceptionState) <= 0 && !exceptionState.hadException();
    480     if (exceptionState.hadException())
    481         return false;
    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->countChildren(), exceptionState);
    501 }
    502 
    503 String DOMSelection::toString()
    504 {
    505     if (!m_frame)
    506         return String();
    507 
    508     Position start, end;
    509     if (m_frame->selection().selection().toNormalizedPositions(start, end))
    510         return plainText(start, end);
    511     return emptyString();
    512 }
    513 
    514 Node* DOMSelection::shadowAdjustedNode(const Position& position) const
    515 {
    516     if (position.isNull())
    517         return 0;
    518 
    519     Node* containerNode = position.containerNode();
    520     Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
    521 
    522     if (!adjustedNode)
    523         return 0;
    524 
    525     if (containerNode == adjustedNode)
    526         return containerNode;
    527 
    528     ASSERT(!adjustedNode->isShadowRoot());
    529     return adjustedNode->parentOrShadowHostNode();
    530 }
    531 
    532 int DOMSelection::shadowAdjustedOffset(const Position& position) const
    533 {
    534     if (position.isNull())
    535         return 0;
    536 
    537     Node* containerNode = position.containerNode();
    538     Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
    539 
    540     if (!adjustedNode)
    541         return 0;
    542 
    543     if (containerNode == adjustedNode)
    544         return position.computeOffsetInContainerNode();
    545 
    546     return adjustedNode->nodeIndex();
    547 }
    548 
    549 bool DOMSelection::isValidForPosition(Node* node) const
    550 {
    551     ASSERT(m_frame);
    552     if (!node)
    553         return true;
    554     return node->document() == m_frame->document();
    555 }
    556 
    557 void DOMSelection::addConsoleError(const String& message)
    558 {
    559     if (m_treeScope)
    560         m_treeScope->document().addConsoleMessage(ConsoleMessage::create(JSMessageSource, ErrorMessageLevel, message));
    561 }
    562 
    563 void DOMSelection::trace(Visitor* visitor)
    564 {
    565     visitor->trace(m_treeScope);
    566     DOMWindowProperty::trace(visitor);
    567 }
    568 
    569 } // namespace blink
    570