2 * Copyright (C) 2007, 2009 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
31 #include "DOMSelection.h"
33 #include "ExceptionCode.h"
36 #include "PlatformString.h"
38 #include "SelectionController.h"
39 #include "TextIterator.h"
40 #include "htmlediting.h"
44 static Node* selectionShadowAncestor(Frame* frame)
46 Node* node = frame->selection()->selection().base().anchorNode();
49 Node* shadowAncestor = node->shadowAncestorNode();
50 if (shadowAncestor == node)
52 return shadowAncestor;
55 DOMSelection::DOMSelection(Frame* frame)
60 Frame* DOMSelection::frame() const
65 void DOMSelection::disconnectFrame()
70 const VisibleSelection& DOMSelection::visibleSelection() const
73 return m_frame->selection()->selection();
76 static Position anchorPosition(const VisibleSelection& selection)
78 Position anchor = selection.isBaseFirst() ? selection.start() : selection.end();
79 return anchor.parentAnchoredEquivalent();
82 static Position focusPosition(const VisibleSelection& selection)
84 Position focus = selection.isBaseFirst() ? selection.end() : selection.start();
85 return focus.parentAnchoredEquivalent();
88 static Position basePosition(const VisibleSelection& selection)
90 return selection.base().parentAnchoredEquivalent();
93 static Position extentPosition(const VisibleSelection& selection)
95 return selection.extent().parentAnchoredEquivalent();
98 Node* DOMSelection::anchorNode() const
102 if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
103 return shadowAncestor->parentNodeGuaranteedHostFree();
104 return anchorPosition(visibleSelection()).containerNode();
107 int DOMSelection::anchorOffset() const
111 if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
112 return shadowAncestor->nodeIndex();
113 return anchorPosition(visibleSelection()).offsetInContainerNode();
116 Node* DOMSelection::focusNode() const
120 if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
121 return shadowAncestor->parentNodeGuaranteedHostFree();
122 return focusPosition(visibleSelection()).containerNode();
125 int DOMSelection::focusOffset() const
129 if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
130 return shadowAncestor->nodeIndex();
131 return focusPosition(visibleSelection()).offsetInContainerNode();
134 Node* DOMSelection::baseNode() const
138 if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
139 return shadowAncestor->parentNodeGuaranteedHostFree();
140 return basePosition(visibleSelection()).containerNode();
143 int DOMSelection::baseOffset() const
147 if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
148 return shadowAncestor->nodeIndex();
149 return basePosition(visibleSelection()).offsetInContainerNode();
152 Node* DOMSelection::extentNode() const
156 if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
157 return shadowAncestor->parentNodeGuaranteedHostFree();
158 return extentPosition(visibleSelection()).containerNode();
161 int DOMSelection::extentOffset() const
165 if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
166 return shadowAncestor->nodeIndex();
167 return extentPosition(visibleSelection()).offsetInContainerNode();
170 bool DOMSelection::isCollapsed() const
172 if (!m_frame || selectionShadowAncestor(m_frame))
174 return !m_frame->selection()->isRange();
177 String DOMSelection::type() const
182 SelectionController* selection = m_frame->selection();
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())
189 if (selection->isCaret())
194 int DOMSelection::rangeCount() const
198 return m_frame->selection()->isNone() ? 0 : 1;
201 void DOMSelection::collapse(Node* node, int offset, ExceptionCode& ec)
211 if (!isValidForPosition(node))
214 // FIXME: Eliminate legacy editing positions
215 m_frame->selection()->moveTo(VisiblePosition(Position(node, offset), DOWNSTREAM));
218 void DOMSelection::collapseToEnd(ExceptionCode& ec)
223 const VisibleSelection& selection = m_frame->selection()->selection();
225 if (selection.isNone()) {
226 ec = INVALID_STATE_ERR;
230 m_frame->selection()->moveTo(VisiblePosition(selection.end(), DOWNSTREAM));
233 void DOMSelection::collapseToStart(ExceptionCode& ec)
238 const VisibleSelection& selection = m_frame->selection()->selection();
240 if (selection.isNone()) {
241 ec = INVALID_STATE_ERR;
245 m_frame->selection()->moveTo(VisiblePosition(selection.start(), DOWNSTREAM));
248 void DOMSelection::empty()
252 m_frame->selection()->clear();
255 void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionCode& ec)
260 if (baseOffset < 0 || extentOffset < 0) {
265 if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode))
268 // FIXME: Eliminate legacy editing positions
269 VisiblePosition visibleBase = VisiblePosition(Position(baseNode, baseOffset), DOWNSTREAM);
270 VisiblePosition visibleExtent = VisiblePosition(Position(extentNode, extentOffset), DOWNSTREAM);
272 m_frame->selection()->moveTo(visibleBase, visibleExtent);
275 void DOMSelection::setPosition(Node* node, int offset, ExceptionCode& ec)
284 if (!isValidForPosition(node))
287 // FIXME: Eliminate legacy editing positions
288 m_frame->selection()->moveTo(VisiblePosition(Position(node, offset), DOWNSTREAM));
291 void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString)
296 SelectionController::EAlteration alter;
297 if (equalIgnoringCase(alterString, "extend"))
298 alter = SelectionController::AlterationExtend;
299 else if (equalIgnoringCase(alterString, "move"))
300 alter = SelectionController::AlterationMove;
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;
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;
340 m_frame->selection()->modify(alter, direction, granularity, false);
343 void DOMSelection::extend(Node* node, int offset, ExceptionCode& ec)
349 ec = TYPE_MISMATCH_ERR;
353 if (offset < 0 || offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->childNodeCount())) {
358 if (!isValidForPosition(node))
361 // FIXME: Eliminate legacy editing positions
362 m_frame->selection()->setExtent(VisiblePosition(Position(node, offset), DOWNSTREAM));
365 PassRefPtr<Range> DOMSelection::getRangeAt(int index, ExceptionCode& ec)
370 if (index < 0 || index >= rangeCount()) {
375 // If you're hitting this, you've added broken multi-range selection support
376 ASSERT(rangeCount() == 1);
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);
384 const VisibleSelection& selection = m_frame->selection()->selection();
385 return selection.firstRange();
388 void DOMSelection::removeAllRanges()
392 m_frame->selection()->clear();
395 void DOMSelection::addRange(Range* r)
402 SelectionController* selection = m_frame->selection();
404 if (selection->isNone()) {
405 selection->setSelection(VisibleSelection(r));
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));
418 // r contains the original range.
419 selection->setSelection(VisibleSelection(r));
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()));
428 // The original range and r intersect.
429 selection->setSelection(VisibleSelection(range->startPosition(), r->endPosition(), DOWNSTREAM));
434 void DOMSelection::deleteFromDocument()
439 SelectionController* selection = m_frame->selection();
441 if (selection->isNone())
445 selection->modify(SelectionController::AlterationExtend, DirectionBackward, CharacterGranularity);
447 RefPtr<Range> selectedRange = selection->selection().toNormalizedRange();
451 ExceptionCode ec = 0;
452 selectedRange->deleteContents(ec);
455 setBaseAndExtent(selectedRange->startContainer(ec), selectedRange->startOffset(ec), selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec);
459 bool DOMSelection::containsNode(const Node* n, bool allowPartial) const
464 SelectionController* selection = m_frame->selection();
466 if (!n || m_frame->document() != n->document() || selection->isNone())
469 ContainerNode* parentNode = n->parentNode();
470 unsigned nodeIndex = n->nodeIndex();
471 RefPtr<Range> selectedRange = selection->selection().toNormalizedRange();
476 ExceptionCode ec = 0;
477 bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(ec), selectedRange->startOffset(ec)) >= 0
478 && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(ec), selectedRange->endOffset(ec)) <= 0;
480 if (nodeFullySelected)
483 bool nodeFullyUnselected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->endContainer(ec), selectedRange->endOffset(ec)) > 0
484 || Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->startContainer(ec), selectedRange->startOffset(ec)) < 0;
486 if (nodeFullyUnselected)
489 return allowPartial || n->isTextNode();
492 void DOMSelection::selectAllChildren(Node* n, ExceptionCode& ec)
497 // This doesn't (and shouldn't) select text node characters.
498 setBaseAndExtent(n, 0, n, n->childNodeCount(), ec);
501 String DOMSelection::toString()
506 return plainText(m_frame->selection()->selection().toNormalizedRange().get());
509 bool DOMSelection::isValidForPosition(Node* node) const
514 return node->document() == m_frame->document();
517 } // namespace WebCore