2 * Copyright (C) 2007, 2009 Apple Inc. All rights reserved.
3 * Copyright (C) 2012 Google Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
32 #include "DOMSelection.h"
35 #include "ExceptionCode.h"
37 #include "FrameSelection.h"
39 #include "PlatformString.h"
41 #include "TextIterator.h"
42 #include "TreeScope.h"
43 #include "htmlediting.h"
47 static Node* selectionShadowAncestor(Frame* frame)
49 Node* node = frame->selection()->selection().base().anchorNode();
53 if (!node->isInShadowTree())
56 Node* shadowAncestor = node->shadowAncestorNode();
57 while (shadowAncestor->isInShadowTree())
58 shadowAncestor = shadowAncestor->shadowAncestorNode();
59 return shadowAncestor;
62 DOMSelection::DOMSelection(const TreeScope* treeScope)
63 : DOMWindowProperty(treeScope->rootNode()->document()->frame())
64 , m_treeScope(treeScope)
68 void DOMSelection::clearTreeScope()
73 const VisibleSelection& DOMSelection::visibleSelection() const
76 return m_frame->selection()->selection();
79 static Position anchorPosition(const VisibleSelection& selection)
81 Position anchor = selection.isBaseFirst() ? selection.start() : selection.end();
82 return anchor.parentAnchoredEquivalent();
85 static Position focusPosition(const VisibleSelection& selection)
87 Position focus = selection.isBaseFirst() ? selection.end() : selection.start();
88 return focus.parentAnchoredEquivalent();
91 static Position basePosition(const VisibleSelection& selection)
93 return selection.base().parentAnchoredEquivalent();
96 static Position extentPosition(const VisibleSelection& selection)
98 return selection.extent().parentAnchoredEquivalent();
101 Node* DOMSelection::anchorNode() const
106 return shadowAdjustedNode(anchorPosition(visibleSelection()));
109 int DOMSelection::anchorOffset() const
114 return shadowAdjustedOffset(anchorPosition(visibleSelection()));
117 Node* DOMSelection::focusNode() const
122 return shadowAdjustedNode(focusPosition(visibleSelection()));
125 int DOMSelection::focusOffset() const
130 return shadowAdjustedOffset(focusPosition(visibleSelection()));
133 Node* DOMSelection::baseNode() const
138 return shadowAdjustedNode(basePosition(visibleSelection()));
141 int DOMSelection::baseOffset() const
146 return shadowAdjustedOffset(basePosition(visibleSelection()));
149 Node* DOMSelection::extentNode() const
154 return shadowAdjustedNode(extentPosition(visibleSelection()));
157 int DOMSelection::extentOffset() const
162 return shadowAdjustedOffset(extentPosition(visibleSelection()));
165 bool DOMSelection::isCollapsed() const
167 if (!m_frame || selectionShadowAncestor(m_frame))
169 return !m_frame->selection()->isRange();
172 String DOMSelection::type() const
177 FrameSelection* selection = m_frame->selection();
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())
184 if (selection->isCaret())
189 int DOMSelection::rangeCount() const
193 return m_frame->selection()->isNone() ? 0 : 1;
196 void DOMSelection::collapse(Node* node, int offset, ExceptionCode& ec)
206 if (!isValidForPosition(node))
209 // FIXME: Eliminate legacy editing positions
210 m_frame->selection()->moveTo(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
213 void DOMSelection::collapseToEnd(ExceptionCode& ec)
218 const VisibleSelection& selection = m_frame->selection()->selection();
220 if (selection.isNone()) {
221 ec = INVALID_STATE_ERR;
225 m_frame->selection()->moveTo(VisiblePosition(selection.end(), DOWNSTREAM));
228 void DOMSelection::collapseToStart(ExceptionCode& ec)
233 const VisibleSelection& selection = m_frame->selection()->selection();
235 if (selection.isNone()) {
236 ec = INVALID_STATE_ERR;
240 m_frame->selection()->moveTo(VisiblePosition(selection.start(), DOWNSTREAM));
243 void DOMSelection::empty()
247 m_frame->selection()->clear();
250 void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionCode& ec)
255 if (baseOffset < 0 || extentOffset < 0) {
260 if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode))
263 // FIXME: Eliminate legacy editing positions
264 VisiblePosition visibleBase = VisiblePosition(createLegacyEditingPosition(baseNode, baseOffset), DOWNSTREAM);
265 VisiblePosition visibleExtent = VisiblePosition(createLegacyEditingPosition(extentNode, extentOffset), DOWNSTREAM);
267 m_frame->selection()->moveTo(visibleBase, visibleExtent);
270 void DOMSelection::setPosition(Node* node, int offset, ExceptionCode& ec)
279 if (!isValidForPosition(node))
282 // FIXME: Eliminate legacy editing positions
283 m_frame->selection()->moveTo(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
286 void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString)
291 FrameSelection::EAlteration alter;
292 if (equalIgnoringCase(alterString, "extend"))
293 alter = FrameSelection::AlterationExtend;
294 else if (equalIgnoringCase(alterString, "move"))
295 alter = FrameSelection::AlterationMove;
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;
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;
333 m_frame->selection()->modify(alter, direction, granularity);
336 void DOMSelection::extend(Node* node, int offset, ExceptionCode& ec)
342 ec = TYPE_MISMATCH_ERR;
346 if (offset < 0 || offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->childNodeCount())) {
351 if (!isValidForPosition(node))
354 // FIXME: Eliminate legacy editing positions
355 m_frame->selection()->setExtent(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
358 PassRefPtr<Range> DOMSelection::getRangeAt(int index, ExceptionCode& ec)
363 if (index < 0 || index >= rangeCount()) {
368 // If you're hitting this, you've added broken multi-range selection support
369 ASSERT(rangeCount() == 1);
371 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) {
372 ContainerNode* container = shadowAncestor->parentNodeGuaranteedHostFree();
373 int offset = shadowAncestor->nodeIndex();
374 return Range::create(shadowAncestor->document(), container, offset, container, offset);
377 const VisibleSelection& selection = m_frame->selection()->selection();
378 return selection.firstRange();
381 void DOMSelection::removeAllRanges()
385 m_frame->selection()->clear();
388 void DOMSelection::addRange(Range* r)
395 FrameSelection* selection = m_frame->selection();
397 if (selection->isNone()) {
398 selection->setSelection(VisibleSelection(r));
402 RefPtr<Range> range = selection->selection().toNormalizedRange();
403 ExceptionCode ec = 0;
404 if (r->compareBoundaryPoints(Range::START_TO_START, range.get(), ec) == -1) {
405 // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
406 if (r->compareBoundaryPoints(Range::START_TO_END, range.get(), ec) > -1) {
407 if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1)
408 // The original range and r intersect.
409 selection->setSelection(VisibleSelection(r->startPosition(), range->endPosition(), DOWNSTREAM));
411 // r contains the original range.
412 selection->setSelection(VisibleSelection(r));
415 // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
416 if (r->compareBoundaryPoints(Range::END_TO_START, range.get(), ec) < 1 && !ec) {
417 if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1)
418 // The original range contains r.
419 selection->setSelection(VisibleSelection(range.get()));
421 // The original range and r intersect.
422 selection->setSelection(VisibleSelection(range->startPosition(), r->endPosition(), DOWNSTREAM));
427 void DOMSelection::deleteFromDocument()
432 FrameSelection* selection = m_frame->selection();
434 if (selection->isNone())
438 selection->modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity);
440 RefPtr<Range> selectedRange = selection->selection().toNormalizedRange();
444 ExceptionCode ec = 0;
445 selectedRange->deleteContents(ec);
448 setBaseAndExtent(selectedRange->startContainer(ec), selectedRange->startOffset(ec), selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec);
452 bool DOMSelection::containsNode(const Node* n, bool allowPartial) const
457 FrameSelection* selection = m_frame->selection();
459 if (!n || m_frame->document() != n->document() || selection->isNone())
462 ContainerNode* parentNode = n->parentNode();
463 unsigned nodeIndex = n->nodeIndex();
464 RefPtr<Range> selectedRange = selection->selection().toNormalizedRange();
469 ExceptionCode ec = 0;
470 bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec) >= 0 && !ec
471 && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(ec), selectedRange->endOffset(ec), ec) <= 0 && !ec;
473 if (nodeFullySelected)
476 bool nodeFullyUnselected = (Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->endContainer(ec), selectedRange->endOffset(ec), ec) > 0 && !ec)
477 || (Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec) < 0 && !ec);
479 if (nodeFullyUnselected)
482 return allowPartial || n->isTextNode();
485 void DOMSelection::selectAllChildren(Node* n, ExceptionCode& ec)
490 // This doesn't (and shouldn't) select text node characters.
491 setBaseAndExtent(n, 0, n, n->childNodeCount(), ec);
494 String DOMSelection::toString()
499 return plainText(m_frame->selection()->selection().toNormalizedRange().get());
502 Node* DOMSelection::shadowAdjustedNode(const Position& position) const
504 if (position.isNull())
507 Node* containerNode = position.containerNode();
508 Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
513 if (containerNode == adjustedNode)
514 return containerNode;
516 return adjustedNode->parentNodeGuaranteedHostFree();
519 int DOMSelection::shadowAdjustedOffset(const Position& position) const
521 if (position.isNull())
524 Node* containerNode = position.containerNode();
525 Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
530 if (containerNode == adjustedNode)
531 return position.computeOffsetInContainerNode();
533 return adjustedNode->nodeIndex();
536 bool DOMSelection::isValidForPosition(Node* node) const
541 return node->document() == m_frame->document();
544 } // namespace WebCore