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 "core/editing/DOMSelection.h"
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"
51 static Node* selectionShadowAncestor(LocalFrame* frame)
53 Node* node = frame->selection().selection().base().anchorNode();
57 if (!node->isInShadowTree())
60 return frame->document()->ancestorInThisScope(node);
63 DOMSelection::DOMSelection(const TreeScope* treeScope)
64 : DOMWindowProperty(treeScope->rootNode().document().frame())
65 , m_treeScope(treeScope)
67 ScriptWrappable::init(this);
70 void DOMSelection::clearTreeScope()
72 m_treeScope = nullptr;
75 const VisibleSelection& DOMSelection::visibleSelection() const
78 return m_frame->selection().selection();
81 static Position anchorPosition(const VisibleSelection& selection)
83 Position anchor = selection.isBaseFirst() ? selection.start() : selection.end();
84 return anchor.parentAnchoredEquivalent();
87 static Position focusPosition(const VisibleSelection& selection)
89 Position focus = selection.isBaseFirst() ? selection.end() : selection.start();
90 return focus.parentAnchoredEquivalent();
93 static Position basePosition(const VisibleSelection& selection)
95 return selection.base().parentAnchoredEquivalent();
98 static Position extentPosition(const VisibleSelection& selection)
100 return selection.extent().parentAnchoredEquivalent();
103 Node* DOMSelection::anchorNode() const
108 return shadowAdjustedNode(anchorPosition(visibleSelection()));
111 int DOMSelection::anchorOffset() const
116 return shadowAdjustedOffset(anchorPosition(visibleSelection()));
119 Node* DOMSelection::focusNode() const
124 return shadowAdjustedNode(focusPosition(visibleSelection()));
127 int DOMSelection::focusOffset() const
132 return shadowAdjustedOffset(focusPosition(visibleSelection()));
135 Node* DOMSelection::baseNode() const
140 return shadowAdjustedNode(basePosition(visibleSelection()));
143 int DOMSelection::baseOffset() const
148 return shadowAdjustedOffset(basePosition(visibleSelection()));
151 Node* DOMSelection::extentNode() const
156 return shadowAdjustedNode(extentPosition(visibleSelection()));
159 int DOMSelection::extentOffset() const
164 return shadowAdjustedOffset(extentPosition(visibleSelection()));
167 bool DOMSelection::isCollapsed() const
169 if (!m_frame || selectionShadowAncestor(m_frame))
171 return !m_frame->selection().isRange();
174 String DOMSelection::type() const
179 FrameSelection& selection = m_frame->selection();
181 // This is a WebKit DOM extension, incompatible with an IE extension
182 // IE has this same attribute, but returns "none", "text" and "control"
183 // http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx
184 if (selection.isNone())
186 if (selection.isCaret())
191 int DOMSelection::rangeCount() const
195 return m_frame->selection().isNone() ? 0 : 1;
198 void DOMSelection::collapse(Node* node, int offset, ExceptionState& exceptionState)
205 exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is not a valid offset.");
209 if (!isValidForPosition(node))
211 RefPtrWillBeRawPtr<Range> range = Range::create(node->document());
212 range->setStart(node, offset, exceptionState);
213 if (exceptionState.hadException())
215 range->setEnd(node, offset, exceptionState);
216 if (exceptionState.hadException())
218 m_frame->selection().setSelectedRange(range.get(), DOWNSTREAM, m_frame->selection().isDirectional() ? FrameSelection::Directional : FrameSelection::NonDirectional);
221 void DOMSelection::collapse(Node* node, ExceptionState& exceptionState)
223 collapse(node, 0, exceptionState);
226 void DOMSelection::collapseToEnd(ExceptionState& exceptionState)
231 const VisibleSelection& selection = m_frame->selection().selection();
233 if (selection.isNone()) {
234 exceptionState.throwDOMException(InvalidStateError, "there is no selection.");
238 m_frame->selection().moveTo(VisiblePosition(selection.end(), DOWNSTREAM));
241 void DOMSelection::collapseToStart(ExceptionState& exceptionState)
246 const VisibleSelection& selection = m_frame->selection().selection();
248 if (selection.isNone()) {
249 exceptionState.throwDOMException(InvalidStateError, "there is no selection.");
253 m_frame->selection().moveTo(VisiblePosition(selection.start(), DOWNSTREAM));
256 void DOMSelection::empty()
260 m_frame->selection().clear();
263 void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionState& exceptionState)
268 if (baseOffset < 0) {
269 exceptionState.throwDOMException(IndexSizeError, String::number(baseOffset) + " is not a valid base offset.");
273 if (extentOffset < 0) {
274 exceptionState.throwDOMException(IndexSizeError, String::number(extentOffset) + " is not a valid extent offset.");
278 if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode))
281 // FIXME: Eliminate legacy editing positions
282 VisiblePosition visibleBase = VisiblePosition(createLegacyEditingPosition(baseNode, baseOffset), DOWNSTREAM);
283 VisiblePosition visibleExtent = VisiblePosition(createLegacyEditingPosition(extentNode, extentOffset), DOWNSTREAM);
285 m_frame->selection().moveTo(visibleBase, visibleExtent);
288 void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString)
293 FrameSelection::EAlteration alter;
294 if (equalIgnoringCase(alterString, "extend"))
295 alter = FrameSelection::AlterationExtend;
296 else if (equalIgnoringCase(alterString, "move"))
297 alter = FrameSelection::AlterationMove;
301 SelectionDirection direction;
302 if (equalIgnoringCase(directionString, "forward"))
303 direction = DirectionForward;
304 else if (equalIgnoringCase(directionString, "backward"))
305 direction = DirectionBackward;
306 else if (equalIgnoringCase(directionString, "left"))
307 direction = DirectionLeft;
308 else if (equalIgnoringCase(directionString, "right"))
309 direction = DirectionRight;
313 TextGranularity granularity;
314 if (equalIgnoringCase(granularityString, "character"))
315 granularity = CharacterGranularity;
316 else if (equalIgnoringCase(granularityString, "word"))
317 granularity = WordGranularity;
318 else if (equalIgnoringCase(granularityString, "sentence"))
319 granularity = SentenceGranularity;
320 else if (equalIgnoringCase(granularityString, "line"))
321 granularity = LineGranularity;
322 else if (equalIgnoringCase(granularityString, "paragraph"))
323 granularity = ParagraphGranularity;
324 else if (equalIgnoringCase(granularityString, "lineboundary"))
325 granularity = LineBoundary;
326 else if (equalIgnoringCase(granularityString, "sentenceboundary"))
327 granularity = SentenceBoundary;
328 else if (equalIgnoringCase(granularityString, "paragraphboundary"))
329 granularity = ParagraphBoundary;
330 else if (equalIgnoringCase(granularityString, "documentboundary"))
331 granularity = DocumentBoundary;
335 m_frame->selection().modify(alter, direction, granularity);
338 void DOMSelection::extend(Node* node, int offset, ExceptionState& exceptionState)
346 exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is not a valid offset.");
349 if (offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->countChildren())) {
350 exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is larger than the given node's length.");
354 if (!isValidForPosition(node))
357 // FIXME: Eliminate legacy editing positions
358 m_frame->selection().setExtent(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
361 void DOMSelection::extend(Node* node, ExceptionState& exceptionState)
363 // This default value implementation differs from the spec, which says |offset| is not optional.
364 // FIXME: Specify this default value in Selection.idl.
365 extend(node, 0, exceptionState);
368 PassRefPtrWillBeRawPtr<Range> DOMSelection::getRangeAt(int index, ExceptionState& exceptionState)
373 if (index < 0 || index >= rangeCount()) {
374 exceptionState.throwDOMException(IndexSizeError, String::number(index) + " is not a valid index.");
378 // If you're hitting this, you've added broken multi-range selection support
379 ASSERT(rangeCount() == 1);
381 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) {
382 ASSERT(!shadowAncestor->isShadowRoot());
383 ContainerNode* container = shadowAncestor->parentOrShadowHostNode();
384 int offset = shadowAncestor->nodeIndex();
385 return Range::create(shadowAncestor->document(), container, offset, container, offset);
388 return m_frame->selection().firstRange();
391 void DOMSelection::removeAllRanges()
395 m_frame->selection().clear();
398 void DOMSelection::addRange(Range* newRange)
403 // FIXME: Should we throw DOMException for error cases below?
405 addConsoleError("The given range is null.");
409 if (!newRange->startContainer()) {
410 addConsoleError("The given range has no container. Perhaps 'detach()' has been invoked on it?");
414 FrameSelection& selection = m_frame->selection();
416 if (selection.isNone()) {
417 selection.setSelectedRange(newRange, VP_DEFAULT_AFFINITY);
421 RefPtrWillBeRawPtr<Range> originalRange = selection.firstRange();
423 if (originalRange->startContainer()->document() != newRange->startContainer()->document()) {
424 addConsoleError("The given range does not belong to the current selection's document.");
427 if (originalRange->startContainer()->treeScope() != newRange->startContainer()->treeScope()) {
428 addConsoleError("The given range and the current selection belong to two different document fragments.");
432 if (originalRange->compareBoundaryPoints(Range::START_TO_END, newRange, ASSERT_NO_EXCEPTION) < 0
433 || newRange->compareBoundaryPoints(Range::START_TO_END, originalRange.get(), ASSERT_NO_EXCEPTION) < 0) {
434 addConsoleError("Discontiguous selection is not supported.");
438 // FIXME: "Merge the ranges if they intersect" is Blink-specific behavior; other browsers supporting discontiguous
439 // selection (obviously) keep each Range added and return it in getRangeAt(). But it's unclear if we can really
440 // do the same, since we don't support discontiguous selection. Further discussions at
441 // <https://code.google.com/p/chromium/issues/detail?id=353069>.
443 Range* start = originalRange->compareBoundaryPoints(Range::START_TO_START, newRange, ASSERT_NO_EXCEPTION) < 0 ? originalRange.get() : newRange;
444 Range* end = originalRange->compareBoundaryPoints(Range::END_TO_END, newRange, ASSERT_NO_EXCEPTION) < 0 ? newRange : originalRange.get();
445 RefPtrWillBeRawPtr<Range> merged = Range::create(originalRange->startContainer()->document(), start->startContainer(), start->startOffset(), end->endContainer(), end->endOffset());
446 EAffinity affinity = selection.selection().affinity();
447 selection.setSelectedRange(merged.get(), affinity);
450 void DOMSelection::deleteFromDocument()
455 FrameSelection& selection = m_frame->selection();
457 if (selection.isNone())
460 RefPtrWillBeRawPtr<Range> selectedRange = selection.selection().toNormalizedRange();
464 selectedRange->deleteContents(ASSERT_NO_EXCEPTION);
466 setBaseAndExtent(selectedRange->startContainer(), selectedRange->startOffset(), selectedRange->startContainer(), selectedRange->startOffset(), ASSERT_NO_EXCEPTION);
469 bool DOMSelection::containsNode(const Node* n, bool allowPartial) const
474 FrameSelection& selection = m_frame->selection();
476 if (!n || m_frame->document() != n->document() || selection.isNone())
479 unsigned nodeIndex = n->nodeIndex();
480 RefPtrWillBeRawPtr<Range> selectedRange = selection.selection().toNormalizedRange();
482 ContainerNode* parentNode = n->parentNode();
486 TrackExceptionState exceptionState;
487 bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(), selectedRange->startOffset(), exceptionState) >= 0 && !exceptionState.hadException()
488 && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(), selectedRange->endOffset(), exceptionState) <= 0 && !exceptionState.hadException();
489 if (exceptionState.hadException())
491 if (nodeFullySelected)
494 bool nodeFullyUnselected = (Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->endContainer(), selectedRange->endOffset(), exceptionState) > 0 && !exceptionState.hadException())
495 || (Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->startContainer(), selectedRange->startOffset(), exceptionState) < 0 && !exceptionState.hadException());
496 ASSERT(!exceptionState.hadException());
497 if (nodeFullyUnselected)
500 return allowPartial || n->isTextNode();
503 void DOMSelection::selectAllChildren(Node* n, ExceptionState& exceptionState)
508 // This doesn't (and shouldn't) select text node characters.
509 setBaseAndExtent(n, 0, n, n->countChildren(), exceptionState);
512 String DOMSelection::toString()
517 return plainText(m_frame->selection().selection().toNormalizedRange().get());
520 Node* DOMSelection::shadowAdjustedNode(const Position& position) const
522 if (position.isNull())
525 Node* containerNode = position.containerNode();
526 Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
531 if (containerNode == adjustedNode)
532 return containerNode;
534 ASSERT(!adjustedNode->isShadowRoot());
535 return adjustedNode->parentOrShadowHostNode();
538 int DOMSelection::shadowAdjustedOffset(const Position& position) const
540 if (position.isNull())
543 Node* containerNode = position.containerNode();
544 Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
549 if (containerNode == adjustedNode)
550 return position.computeOffsetInContainerNode();
552 return adjustedNode->nodeIndex();
555 bool DOMSelection::isValidForPosition(Node* node) const
560 return node->document() == m_frame->document();
563 void DOMSelection::addConsoleError(const String& message)
566 m_treeScope->document().addConsoleMessage(ConsoleMessage::create(JSMessageSource, ErrorMessageLevel, message));