/* * Copyright (C) 2007, 2009, 2016 Apple Inc. All rights reserved. * Copyright (C) 2012 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "DOMSelection.h" #include "Document.h" #include "ExceptionCode.h" #include "Frame.h" #include "FrameSelection.h" #include "Range.h" #include "TextIterator.h" #include "htmlediting.h" namespace WebCore { static Node* selectionShadowAncestor(Frame& frame) { auto* node = frame.selection().selection().base().anchorNode(); if (!node) return nullptr; if (!node->isInShadowTree()) return nullptr; // FIXME: Unclear on why this needs to be the possibly null frame.document() instead of the never null node->document(). return frame.document()->ancestorNodeInThisScope(node); } DOMSelection::DOMSelection(Frame& frame) : DOMWindowProperty(&frame) { } const VisibleSelection& DOMSelection::visibleSelection() const { ASSERT(m_frame); return m_frame->selection().selection(); } static Position anchorPosition(const VisibleSelection& selection) { auto anchor = selection.isBaseFirst() ? selection.start() : selection.end(); return anchor.parentAnchoredEquivalent(); } static Position focusPosition(const VisibleSelection& selection) { auto focus = selection.isBaseFirst() ? selection.end() : selection.start(); return focus.parentAnchoredEquivalent(); } static Position basePosition(const VisibleSelection& selection) { return selection.base().parentAnchoredEquivalent(); } static Position extentPosition(const VisibleSelection& selection) { return selection.extent().parentAnchoredEquivalent(); } Node* DOMSelection::anchorNode() const { if (!m_frame) return 0; return shadowAdjustedNode(anchorPosition(visibleSelection())); } unsigned DOMSelection::anchorOffset() const { if (!m_frame) return 0; return shadowAdjustedOffset(anchorPosition(visibleSelection())); } Node* DOMSelection::focusNode() const { if (!m_frame) return nullptr; return shadowAdjustedNode(focusPosition(visibleSelection())); } unsigned DOMSelection::focusOffset() const { if (!m_frame) return 0; return shadowAdjustedOffset(focusPosition(visibleSelection())); } Node* DOMSelection::baseNode() const { if (!m_frame) return 0; return shadowAdjustedNode(basePosition(visibleSelection())); } unsigned DOMSelection::baseOffset() const { if (!m_frame) return 0; return shadowAdjustedOffset(basePosition(visibleSelection())); } Node* DOMSelection::extentNode() const { if (!m_frame) return 0; return shadowAdjustedNode(extentPosition(visibleSelection())); } unsigned DOMSelection::extentOffset() const { if (!m_frame) return 0; return shadowAdjustedOffset(extentPosition(visibleSelection())); } bool DOMSelection::isCollapsed() const { if (!m_frame || selectionShadowAncestor(*m_frame)) return true; return !m_frame->selection().isRange(); } String DOMSelection::type() const { if (!m_frame) return ASCIILiteral("None"); auto& selection = m_frame->selection(); if (selection.isNone()) return ASCIILiteral("None"); if (selection.isCaret()) return ASCIILiteral("Caret"); return ASCIILiteral("Range"); } unsigned DOMSelection::rangeCount() const { return !m_frame || m_frame->selection().isNone() ? 0 : 1; } void DOMSelection::collapse(Node* node, unsigned offset) { if (!isValidForPosition(node)) return; Ref protector(*m_frame); m_frame->selection().moveTo(createLegacyEditingPosition(node, offset), DOWNSTREAM); } ExceptionOr DOMSelection::collapseToEnd() { if (!m_frame) return { }; auto& selection = m_frame->selection(); if (selection.isNone()) return Exception { INVALID_STATE_ERR }; Ref protector(*m_frame); selection.moveTo(selection.selection().end(), DOWNSTREAM); return { }; } ExceptionOr DOMSelection::collapseToStart() { if (!m_frame) return { }; auto& selection = m_frame->selection(); if (selection.isNone()) return Exception { INVALID_STATE_ERR }; Ref protector(*m_frame); selection.moveTo(selection.selection().start(), DOWNSTREAM); return { }; } void DOMSelection::empty() { if (!m_frame) return; m_frame->selection().clear(); } void DOMSelection::setBaseAndExtent(Node* baseNode, unsigned baseOffset, Node* extentNode, unsigned extentOffset) { if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode)) return; Ref protector(*m_frame); m_frame->selection().moveTo(createLegacyEditingPosition(baseNode, baseOffset), createLegacyEditingPosition(extentNode, extentOffset), DOWNSTREAM); } void DOMSelection::setPosition(Node* node, unsigned offset) { if (!isValidForPosition(node)) return; Ref protector(*m_frame); m_frame->selection().moveTo(createLegacyEditingPosition(node, offset), DOWNSTREAM); } void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString) { if (!m_frame) return; FrameSelection::EAlteration alter; if (equalLettersIgnoringASCIICase(alterString, "extend")) alter = FrameSelection::AlterationExtend; else if (equalLettersIgnoringASCIICase(alterString, "move")) alter = FrameSelection::AlterationMove; else return; SelectionDirection direction; if (equalLettersIgnoringASCIICase(directionString, "forward")) direction = DirectionForward; else if (equalLettersIgnoringASCIICase(directionString, "backward")) direction = DirectionBackward; else if (equalLettersIgnoringASCIICase(directionString, "left")) direction = DirectionLeft; else if (equalLettersIgnoringASCIICase(directionString, "right")) direction = DirectionRight; else return; TextGranularity granularity; if (equalLettersIgnoringASCIICase(granularityString, "character")) granularity = CharacterGranularity; else if (equalLettersIgnoringASCIICase(granularityString, "word")) granularity = WordGranularity; else if (equalLettersIgnoringASCIICase(granularityString, "sentence")) granularity = SentenceGranularity; else if (equalLettersIgnoringASCIICase(granularityString, "line")) granularity = LineGranularity; else if (equalLettersIgnoringASCIICase(granularityString, "paragraph")) granularity = ParagraphGranularity; else if (equalLettersIgnoringASCIICase(granularityString, "lineboundary")) granularity = LineBoundary; else if (equalLettersIgnoringASCIICase(granularityString, "sentenceboundary")) granularity = SentenceBoundary; else if (equalLettersIgnoringASCIICase(granularityString, "paragraphboundary")) granularity = ParagraphBoundary; else if (equalLettersIgnoringASCIICase(granularityString, "documentboundary")) granularity = DocumentBoundary; else return; Ref protector(*m_frame); m_frame->selection().modify(alter, direction, granularity); } ExceptionOr DOMSelection::extend(Node& node, unsigned offset) { if (!m_frame) return { }; if (offset > (node.offsetInCharacters() ? caretMaxOffset(node) : node.countChildNodes())) return Exception { INDEX_SIZE_ERR }; if (!isValidForPosition(&node)) return { }; Ref protector(*m_frame); m_frame->selection().setExtent(createLegacyEditingPosition(&node, offset), DOWNSTREAM); return { }; } ExceptionOr> DOMSelection::getRangeAt(unsigned index) { if (index >= rangeCount()) return Exception { INDEX_SIZE_ERR }; // If you're hitting this, you've added broken multi-range selection support. ASSERT(rangeCount() == 1); if (auto* shadowAncestor = selectionShadowAncestor(*m_frame)) { auto* container = shadowAncestor->parentNodeGuaranteedHostFree(); unsigned offset = shadowAncestor->computeNodeIndex(); return Range::create(shadowAncestor->document(), container, offset, container, offset); } auto firstRange = m_frame->selection().selection().firstRange(); ASSERT(firstRange); if (!firstRange) return Exception { INDEX_SIZE_ERR }; return firstRange.releaseNonNull(); } void DOMSelection::removeAllRanges() { if (!m_frame) return; m_frame->selection().clear(); } void DOMSelection::addRange(Range& range) { if (!m_frame) return; Ref protector(*m_frame); auto& selection = m_frame->selection(); if (selection.isNone()) { selection.moveTo(&range); return; } auto normalizedRange = selection.selection().toNormalizedRange(); if (!normalizedRange) return; auto result = range.compareBoundaryPoints(Range::START_TO_START, *normalizedRange); if (!result.hasException() && result.releaseReturnValue() == -1) { // We don't support discontiguous selection. We don't do anything if the two ranges don't intersect. result = range.compareBoundaryPoints(Range::START_TO_END, *normalizedRange); if (!result.hasException() && result.releaseReturnValue() > -1) { result = range.compareBoundaryPoints(Range::END_TO_END, *normalizedRange); if (!result.hasException() && result.releaseReturnValue() == -1) { // The ranges intersect. selection.moveTo(range.startPosition(), normalizedRange->endPosition(), DOWNSTREAM); } else { // The new range contains the original range. selection.moveTo(&range); } } } else { // We don't support discontiguous selection. We don't do anything if the two ranges don't intersect. result = range.compareBoundaryPoints(Range::END_TO_START, *normalizedRange); if (!result.hasException() && result.releaseReturnValue() < 1) { result = range.compareBoundaryPoints(Range::END_TO_END, *normalizedRange); if (!result.hasException() && result.releaseReturnValue() == -1) { // The original range contains the new range. selection.moveTo(normalizedRange.get()); } else { // The ranges intersect. selection.moveTo(normalizedRange->startPosition(), range.endPosition(), DOWNSTREAM); } } } } void DOMSelection::deleteFromDocument() { if (!m_frame) return; auto& selection = m_frame->selection(); if (selection.isNone()) return; auto selectedRange = selection.selection().toNormalizedRange(); if (!selectedRange || selectedRange->shadowRoot()) return; Ref protector(*m_frame); selectedRange->deleteContents(); setBaseAndExtent(&selectedRange->startContainer(), selectedRange->startOffset(), &selectedRange->startContainer(), selectedRange->startOffset()); } bool DOMSelection::containsNode(Node& node, bool allowPartial) const { if (!m_frame) return false; auto& selection = m_frame->selection(); if (m_frame->document() != &node.document() || selection.isNone()) return false; Ref protectedNode(node); auto selectedRange = selection.selection().toNormalizedRange(); ContainerNode* parentNode = node.parentNode(); if (!parentNode || !parentNode->isConnected()) return false; unsigned nodeIndex = node.computeNodeIndex(); auto startsResult = Range::compareBoundaryPoints(parentNode, nodeIndex, &selectedRange->startContainer(), selectedRange->startOffset()); ASSERT(!startsResult.hasException()); auto endsResult = Range::compareBoundaryPoints(parentNode, nodeIndex + 1, &selectedRange->endContainer(), selectedRange->endOffset()); ASSERT(!endsResult.hasException()); bool isNodeFullySelected = !startsResult.hasException() && startsResult.releaseReturnValue() >= 0 && !endsResult.hasException() && endsResult.releaseReturnValue() <= 0; if (isNodeFullySelected) return true; auto startEndResult = Range::compareBoundaryPoints(parentNode, nodeIndex, &selectedRange->endContainer(), selectedRange->endOffset()); ASSERT(!startEndResult.hasException()); auto endStartResult = Range::compareBoundaryPoints(parentNode, nodeIndex + 1, &selectedRange->startContainer(), selectedRange->startOffset()); ASSERT(!endStartResult.hasException()); bool isNodeFullyUnselected = (!startEndResult.hasException() && startEndResult.releaseReturnValue() > 0) || (!endStartResult.hasException() && endStartResult.releaseReturnValue() < 0); if (isNodeFullyUnselected) return false; return allowPartial || node.isTextNode(); } void DOMSelection::selectAllChildren(Node& node) { // This doesn't (and shouldn't) select text node characters. setBaseAndExtent(&node, 0, &node, node.countChildNodes()); } String DOMSelection::toString() { if (!m_frame) return String(); return plainText(m_frame->selection().selection().toNormalizedRange().get()); } Node* DOMSelection::shadowAdjustedNode(const Position& position) const { if (position.isNull()) return nullptr; auto* containerNode = position.containerNode(); auto* adjustedNode = m_frame->document()->ancestorNodeInThisScope(containerNode); if (!adjustedNode) return nullptr; if (containerNode == adjustedNode) return containerNode; return adjustedNode->parentNodeGuaranteedHostFree(); } unsigned DOMSelection::shadowAdjustedOffset(const Position& position) const { if (position.isNull()) return 0; auto* containerNode = position.containerNode(); auto* adjustedNode = m_frame->document()->ancestorNodeInThisScope(containerNode); if (!adjustedNode) return 0; if (containerNode == adjustedNode) return position.computeOffsetInContainerNode(); return adjustedNode->computeNodeIndex(); } bool DOMSelection::isValidForPosition(Node* node) const { if (!m_frame) return false; if (!node) return true; return &node->document() == m_frame->document(); } } // namespace WebCore