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 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 "TextIterator.h"
40 #include "htmlediting.h"
44 static Node* selectionShadowAncestor(Frame* frame)
46 Node* node = frame->selection().selection().base().anchorNode();
50 if (!node->isInShadowTree())
53 return frame->document()->ancestorInThisScope(node);
56 DOMSelection::DOMSelection(const TreeScope* treeScope)
57 : DOMWindowProperty(treeScope->rootNode().document().frame())
58 , m_treeScope(treeScope)
62 void DOMSelection::clearTreeScope()
64 m_treeScope = nullptr;
67 const VisibleSelection& DOMSelection::visibleSelection() const
70 return m_frame->selection().selection();
73 static Position anchorPosition(const VisibleSelection& selection)
75 Position anchor = selection.isBaseFirst() ? selection.start() : selection.end();
76 return anchor.parentAnchoredEquivalent();
79 static Position focusPosition(const VisibleSelection& selection)
81 Position focus = selection.isBaseFirst() ? selection.end() : selection.start();
82 return focus.parentAnchoredEquivalent();
85 static Position basePosition(const VisibleSelection& selection)
87 return selection.base().parentAnchoredEquivalent();
90 static Position extentPosition(const VisibleSelection& selection)
92 return selection.extent().parentAnchoredEquivalent();
95 Node* DOMSelection::anchorNode() const
100 return shadowAdjustedNode(anchorPosition(visibleSelection()));
103 unsigned DOMSelection::anchorOffset() const
108 return shadowAdjustedOffset(anchorPosition(visibleSelection()));
111 Node* DOMSelection::focusNode() const
116 return shadowAdjustedNode(focusPosition(visibleSelection()));
119 unsigned DOMSelection::focusOffset() const
124 return shadowAdjustedOffset(focusPosition(visibleSelection()));
127 Node* DOMSelection::baseNode() const
132 return shadowAdjustedNode(basePosition(visibleSelection()));
135 unsigned DOMSelection::baseOffset() const
140 return shadowAdjustedOffset(basePosition(visibleSelection()));
143 Node* DOMSelection::extentNode() const
148 return shadowAdjustedNode(extentPosition(visibleSelection()));
151 unsigned DOMSelection::extentOffset() const
156 return shadowAdjustedOffset(extentPosition(visibleSelection()));
159 bool DOMSelection::isCollapsed() const
161 if (!m_frame || selectionShadowAncestor(m_frame))
163 return !m_frame->selection().isRange();
166 String DOMSelection::type() const
171 FrameSelection& selection = m_frame->selection();
173 // This is a WebKit DOM extension, incompatible with an IE extension
174 // IE has this same attribute, but returns "none", "text" and "control"
175 // http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx
176 if (selection.isNone())
178 if (selection.isCaret())
183 unsigned DOMSelection::rangeCount() const
187 return m_frame->selection().isNone() ? 0 : 1;
190 void DOMSelection::collapse(Node* node, unsigned offset)
195 if (!isValidForPosition(node))
198 // FIXME: Eliminate legacy editing positions
199 m_frame->selection().moveTo(createLegacyEditingPosition(node, offset), DOWNSTREAM);
202 void DOMSelection::collapseToEnd(ExceptionCode& ec)
207 const VisibleSelection& selection = m_frame->selection().selection();
209 if (selection.isNone()) {
210 ec = INVALID_STATE_ERR;
214 m_frame->selection().moveTo(selection.end(), DOWNSTREAM);
217 void DOMSelection::collapseToStart(ExceptionCode& ec)
222 const VisibleSelection& selection = m_frame->selection().selection();
224 if (selection.isNone()) {
225 ec = INVALID_STATE_ERR;
229 m_frame->selection().moveTo(selection.start(), DOWNSTREAM);
232 void DOMSelection::empty()
236 m_frame->selection().clear();
239 void DOMSelection::setBaseAndExtent(Node* baseNode, unsigned baseOffset, Node* extentNode, unsigned extentOffset)
244 if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode))
247 // FIXME: Eliminate legacy editing positions
248 m_frame->selection().moveTo(createLegacyEditingPosition(baseNode, baseOffset), createLegacyEditingPosition(extentNode, extentOffset), DOWNSTREAM);
251 void DOMSelection::setPosition(Node* node, unsigned offset)
256 if (!isValidForPosition(node))
259 // FIXME: Eliminate legacy editing positions
260 m_frame->selection().moveTo(createLegacyEditingPosition(node, offset), DOWNSTREAM);
263 void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString)
268 FrameSelection::EAlteration alter;
269 if (equalLettersIgnoringASCIICase(alterString, "extend"))
270 alter = FrameSelection::AlterationExtend;
271 else if (equalLettersIgnoringASCIICase(alterString, "move"))
272 alter = FrameSelection::AlterationMove;
276 SelectionDirection direction;
277 if (equalLettersIgnoringASCIICase(directionString, "forward"))
278 direction = DirectionForward;
279 else if (equalLettersIgnoringASCIICase(directionString, "backward"))
280 direction = DirectionBackward;
281 else if (equalLettersIgnoringASCIICase(directionString, "left"))
282 direction = DirectionLeft;
283 else if (equalLettersIgnoringASCIICase(directionString, "right"))
284 direction = DirectionRight;
288 TextGranularity granularity;
289 if (equalLettersIgnoringASCIICase(granularityString, "character"))
290 granularity = CharacterGranularity;
291 else if (equalLettersIgnoringASCIICase(granularityString, "word"))
292 granularity = WordGranularity;
293 else if (equalLettersIgnoringASCIICase(granularityString, "sentence"))
294 granularity = SentenceGranularity;
295 else if (equalLettersIgnoringASCIICase(granularityString, "line"))
296 granularity = LineGranularity;
297 else if (equalLettersIgnoringASCIICase(granularityString, "paragraph"))
298 granularity = ParagraphGranularity;
299 else if (equalLettersIgnoringASCIICase(granularityString, "lineboundary"))
300 granularity = LineBoundary;
301 else if (equalLettersIgnoringASCIICase(granularityString, "sentenceboundary"))
302 granularity = SentenceBoundary;
303 else if (equalLettersIgnoringASCIICase(granularityString, "paragraphboundary"))
304 granularity = ParagraphBoundary;
305 else if (equalLettersIgnoringASCIICase(granularityString, "documentboundary"))
306 granularity = DocumentBoundary;
310 m_frame->selection().modify(alter, direction, granularity);
313 void DOMSelection::extend(Node& node, unsigned offset, ExceptionCode& ec)
318 if (offset > (node.offsetInCharacters() ? caretMaxOffset(node) : node.countChildNodes())) {
323 if (!isValidForPosition(&node))
326 // FIXME: Eliminate legacy editing positions
327 m_frame->selection().setExtent(createLegacyEditingPosition(&node, offset), DOWNSTREAM);
330 RefPtr<Range> DOMSelection::getRangeAt(unsigned index, ExceptionCode& ec)
335 if (index >= rangeCount()) {
340 // If you're hitting this, you've added broken multi-range selection support
341 ASSERT(rangeCount() == 1);
343 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) {
344 ContainerNode* container = shadowAncestor->parentNodeGuaranteedHostFree();
345 unsigned offset = shadowAncestor->computeNodeIndex();
346 return Range::create(shadowAncestor->document(), container, offset, container, offset);
349 return m_frame->selection().selection().firstRange();
352 void DOMSelection::removeAllRanges()
356 m_frame->selection().clear();
359 void DOMSelection::addRange(Range& range)
364 FrameSelection& selection = m_frame->selection();
365 if (selection.isNone()) {
366 selection.moveTo(&range);
370 RefPtr<Range> normalizedRange = selection.selection().toNormalizedRange();
371 if (!normalizedRange)
374 if (range.compareBoundaryPoints(Range::START_TO_START, *normalizedRange, IGNORE_EXCEPTION) == -1) {
375 // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
376 if (range.compareBoundaryPoints(Range::START_TO_END, *normalizedRange, IGNORE_EXCEPTION) > -1) {
377 if (range.compareBoundaryPoints(Range::END_TO_END, *normalizedRange, IGNORE_EXCEPTION) == -1) {
378 // The original range and r intersect.
379 selection.moveTo(range.startPosition(), normalizedRange->endPosition(), DOWNSTREAM);
381 // r contains the original range.
382 selection.moveTo(&range);
386 // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
387 ExceptionCode ec = 0;
388 if (range.compareBoundaryPoints(Range::END_TO_START, *normalizedRange, ec) < 1 && !ec) {
389 if (range.compareBoundaryPoints(Range::END_TO_END, *normalizedRange, IGNORE_EXCEPTION) == -1) {
390 // The original range contains r.
391 selection.moveTo(normalizedRange.get());
393 // The original range and r intersect.
394 selection.moveTo(normalizedRange->startPosition(), range.endPosition(), DOWNSTREAM);
400 void DOMSelection::deleteFromDocument()
405 FrameSelection& selection = m_frame->selection();
407 if (selection.isNone())
410 RefPtr<Range> selectedRange = selection.selection().toNormalizedRange();
414 selectedRange->deleteContents(ASSERT_NO_EXCEPTION);
416 setBaseAndExtent(&selectedRange->startContainer(), selectedRange->startOffset(), &selectedRange->startContainer(), selectedRange->startOffset());
419 bool DOMSelection::containsNode(Node& node, bool allowPartial) const
424 FrameSelection& selection = m_frame->selection();
426 if (m_frame->document() != &node.document() || selection.isNone())
429 Ref<Node> protectedNode(node);
430 RefPtr<Range> selectedRange = selection.selection().toNormalizedRange();
432 ContainerNode* parentNode = node.parentNode();
433 if (!parentNode || !parentNode->inDocument())
435 unsigned nodeIndex = node.computeNodeIndex();
437 ExceptionCode ec = 0;
438 bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, &selectedRange->startContainer(), selectedRange->startOffset(), ec) >= 0 && !ec
439 && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, &selectedRange->endContainer(), selectedRange->endOffset(), ec) <= 0 && !ec;
441 if (nodeFullySelected)
444 bool nodeFullyUnselected = (Range::compareBoundaryPoints(parentNode, nodeIndex, &selectedRange->endContainer(), selectedRange->endOffset(), ec) > 0 && !ec)
445 || (Range::compareBoundaryPoints(parentNode, nodeIndex + 1, &selectedRange->startContainer(), selectedRange->startOffset(), ec) < 0 && !ec);
447 if (nodeFullyUnselected)
450 return allowPartial || node.isTextNode();
453 void DOMSelection::selectAllChildren(Node& node)
455 // This doesn't (and shouldn't) select text node characters.
456 setBaseAndExtent(&node, 0, &node, node.countChildNodes());
459 String DOMSelection::toString()
464 return plainText(m_frame->selection().selection().toNormalizedRange().get());
467 Node* DOMSelection::shadowAdjustedNode(const Position& position) const
469 if (position.isNull())
472 Node* containerNode = position.containerNode();
473 Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
478 if (containerNode == adjustedNode)
479 return containerNode;
481 return adjustedNode->parentNodeGuaranteedHostFree();
484 unsigned DOMSelection::shadowAdjustedOffset(const Position& position) const
486 if (position.isNull())
489 Node* containerNode = position.containerNode();
490 Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
495 if (containerNode == adjustedNode)
496 return position.computeOffsetInContainerNode();
498 return adjustedNode->computeNodeIndex();
501 bool DOMSelection::isValidForPosition(Node* node) const
506 return &node->document() == m_frame->document();
509 } // namespace WebCore