diff options
Diffstat (limited to 'Source/WebCore/page/DOMSelection.cpp')
-rw-r--r-- | Source/WebCore/page/DOMSelection.cpp | 511 |
1 files changed, 511 insertions, 0 deletions
diff --git a/Source/WebCore/page/DOMSelection.cpp b/Source/WebCore/page/DOMSelection.cpp new file mode 100644 index 0000000..7691da4 --- /dev/null +++ b/Source/WebCore/page/DOMSelection.cpp @@ -0,0 +1,511 @@ +/* + * Copyright (C) 2007, 2009 Apple 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 Computer, 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 "ExceptionCode.h" +#include "Frame.h" +#include "Node.h" +#include "PlatformString.h" +#include "Range.h" +#include "SelectionController.h" +#include "TextIterator.h" +#include "htmlediting.h" + +namespace WebCore { + +static Node* selectionShadowAncestor(Frame* frame) +{ + Node* node = frame->selection()->selection().base().anchorNode(); + if (!node) + return 0; + Node* shadowAncestor = node->shadowAncestorNode(); + if (shadowAncestor == node) + return 0; + return shadowAncestor; +} + +DOMSelection::DOMSelection(Frame* frame) + : m_frame(frame) +{ +} + +Frame* DOMSelection::frame() const +{ + return m_frame; +} + +void DOMSelection::disconnectFrame() +{ + m_frame = 0; +} + +const VisibleSelection& DOMSelection::visibleSelection() const +{ + ASSERT(m_frame); + return m_frame->selection()->selection(); +} + +static Position anchorPosition(const VisibleSelection& selection) +{ + Position anchor = selection.isBaseFirst() ? selection.start() : selection.end(); + return rangeCompliantEquivalent(anchor); +} + +static Position focusPosition(const VisibleSelection& selection) +{ + Position focus = selection.isBaseFirst() ? selection.end() : selection.start(); + return rangeCompliantEquivalent(focus); +} + +static Position basePosition(const VisibleSelection& selection) +{ + return rangeCompliantEquivalent(selection.base()); +} + +static Position extentPosition(const VisibleSelection& selection) +{ + return rangeCompliantEquivalent(selection.extent()); +} + +Node* DOMSelection::anchorNode() const +{ + if (!m_frame) + return 0; + if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) + return shadowAncestor->parentNodeGuaranteedHostFree(); + return anchorPosition(visibleSelection()).node(); +} + +int DOMSelection::anchorOffset() const +{ + if (!m_frame) + return 0; + if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) + return shadowAncestor->nodeIndex(); + return anchorPosition(visibleSelection()).deprecatedEditingOffset(); +} + +Node* DOMSelection::focusNode() const +{ + if (!m_frame) + return 0; + if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) + return shadowAncestor->parentNodeGuaranteedHostFree(); + return focusPosition(visibleSelection()).node(); +} + +int DOMSelection::focusOffset() const +{ + if (!m_frame) + return 0; + if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) + return shadowAncestor->nodeIndex(); + return focusPosition(visibleSelection()).deprecatedEditingOffset(); +} + +Node* DOMSelection::baseNode() const +{ + if (!m_frame) + return 0; + if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) + return shadowAncestor->parentNodeGuaranteedHostFree(); + return basePosition(visibleSelection()).node(); +} + +int DOMSelection::baseOffset() const +{ + if (!m_frame) + return 0; + if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) + return shadowAncestor->nodeIndex(); + return basePosition(visibleSelection()).deprecatedEditingOffset(); +} + +Node* DOMSelection::extentNode() const +{ + if (!m_frame) + return 0; + if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) + return shadowAncestor->parentNodeGuaranteedHostFree(); + return extentPosition(visibleSelection()).node(); +} + +int DOMSelection::extentOffset() const +{ + if (!m_frame) + return 0; + if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) + return shadowAncestor->nodeIndex(); + return extentPosition(visibleSelection()).deprecatedEditingOffset(); +} + +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 String(); + + SelectionController* selection = m_frame->selection(); + + // This is a WebKit DOM extension, incompatible with an IE extension + // IE has this same attribute, but returns "none", "text" and "control" + // http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx + if (selection->isNone()) + return "None"; + if (selection->isCaret()) + return "Caret"; + return "Range"; +} + +int DOMSelection::rangeCount() const +{ + if (!m_frame) + return 0; + return m_frame->selection()->isNone() ? 0 : 1; +} + +void DOMSelection::collapse(Node* node, int offset, ExceptionCode& ec) +{ + if (!m_frame) + return; + + if (offset < 0) { + ec = INDEX_SIZE_ERR; + return; + } + + if (!isValidForPosition(node)) + return; + + m_frame->selection()->moveTo(VisiblePosition(node, offset, DOWNSTREAM)); +} + +void DOMSelection::collapseToEnd(ExceptionCode& ec) +{ + if (!m_frame) + return; + + const VisibleSelection& selection = m_frame->selection()->selection(); + + if (selection.isNone()) { + ec = INVALID_STATE_ERR; + return; + } + + m_frame->selection()->moveTo(VisiblePosition(selection.end(), DOWNSTREAM)); +} + +void DOMSelection::collapseToStart(ExceptionCode& ec) +{ + if (!m_frame) + return; + + const VisibleSelection& selection = m_frame->selection()->selection(); + + if (selection.isNone()) { + ec = INVALID_STATE_ERR; + return; + } + + m_frame->selection()->moveTo(VisiblePosition(selection.start(), DOWNSTREAM)); +} + +void DOMSelection::empty() +{ + if (!m_frame) + return; + m_frame->selection()->clear(); +} + +void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionCode& ec) +{ + if (!m_frame) + return; + + if (baseOffset < 0 || extentOffset < 0) { + ec = INDEX_SIZE_ERR; + return; + } + + if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode)) + return; + + VisiblePosition visibleBase = VisiblePosition(baseNode, baseOffset, DOWNSTREAM); + VisiblePosition visibleExtent = VisiblePosition(extentNode, extentOffset, DOWNSTREAM); + + m_frame->selection()->moveTo(visibleBase, visibleExtent); +} + +void DOMSelection::setPosition(Node* node, int offset, ExceptionCode& ec) +{ + if (!m_frame) + return; + if (offset < 0) { + ec = INDEX_SIZE_ERR; + return; + } + + if (!isValidForPosition(node)) + return; + + m_frame->selection()->moveTo(VisiblePosition(node, offset, DOWNSTREAM)); +} + +void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString) +{ + if (!m_frame) + return; + + SelectionController::EAlteration alter; + if (equalIgnoringCase(alterString, "extend")) + alter = SelectionController::AlterationExtend; + else if (equalIgnoringCase(alterString, "move")) + alter = SelectionController::AlterationMove; + else + return; + + SelectionDirection direction; + if (equalIgnoringCase(directionString, "forward")) + direction = DirectionForward; + else if (equalIgnoringCase(directionString, "backward")) + direction = DirectionBackward; + else if (equalIgnoringCase(directionString, "left")) + direction = DirectionLeft; + else if (equalIgnoringCase(directionString, "right")) + direction = DirectionRight; + else + return; + + TextGranularity granularity; + if (equalIgnoringCase(granularityString, "character")) + granularity = CharacterGranularity; + else if (equalIgnoringCase(granularityString, "word")) + granularity = WordGranularity; + else if (equalIgnoringCase(granularityString, "sentence")) + granularity = SentenceGranularity; + else if (equalIgnoringCase(granularityString, "line")) + granularity = LineGranularity; + else if (equalIgnoringCase(granularityString, "paragraph")) + granularity = ParagraphGranularity; + else if (equalIgnoringCase(granularityString, "lineboundary")) + granularity = LineBoundary; + else if (equalIgnoringCase(granularityString, "sentenceboundary")) + granularity = SentenceBoundary; + else if (equalIgnoringCase(granularityString, "paragraphboundary")) + granularity = ParagraphBoundary; + else if (equalIgnoringCase(granularityString, "documentboundary")) + granularity = DocumentBoundary; + else + return; + + m_frame->selection()->modify(alter, direction, granularity, false); +} + +void DOMSelection::extend(Node* node, int offset, ExceptionCode& ec) +{ + if (!m_frame) + return; + + if (!node) { + ec = TYPE_MISMATCH_ERR; + return; + } + + if (offset < 0 || offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->childNodeCount())) { + ec = INDEX_SIZE_ERR; + return; + } + + if (!isValidForPosition(node)) + return; + + m_frame->selection()->setExtent(VisiblePosition(node, offset, DOWNSTREAM)); +} + +PassRefPtr<Range> DOMSelection::getRangeAt(int index, ExceptionCode& ec) +{ + if (!m_frame) + return 0; + + if (index < 0 || index >= rangeCount()) { + ec = INDEX_SIZE_ERR; + return 0; + } + + // If you're hitting this, you've added broken multi-range selection support + ASSERT(rangeCount() == 1); + + if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) { + ContainerNode* container = shadowAncestor->parentNodeGuaranteedHostFree(); + int offset = shadowAncestor->nodeIndex(); + return Range::create(shadowAncestor->document(), container, offset, container, offset); + } + + const VisibleSelection& selection = m_frame->selection()->selection(); + return selection.firstRange(); +} + +void DOMSelection::removeAllRanges() +{ + if (!m_frame) + return; + m_frame->selection()->clear(); +} + +void DOMSelection::addRange(Range* r) +{ + if (!m_frame) + return; + if (!r) + return; + + SelectionController* selection = m_frame->selection(); + + if (selection->isNone()) { + selection->setSelection(VisibleSelection(r)); + return; + } + + RefPtr<Range> range = selection->selection().toNormalizedRange(); + ExceptionCode ec = 0; + if (r->compareBoundaryPoints(Range::START_TO_START, range.get(), ec) == -1) { + // We don't support discontiguous selection. We don't do anything if r and range don't intersect. + if (r->compareBoundaryPoints(Range::START_TO_END, range.get(), ec) > -1) { + if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1) + // The original range and r intersect. + selection->setSelection(VisibleSelection(r->startPosition(), range->endPosition(), DOWNSTREAM)); + else + // r contains the original range. + selection->setSelection(VisibleSelection(r)); + } + } else { + // We don't support discontiguous selection. We don't do anything if r and range don't intersect. + if (r->compareBoundaryPoints(Range::END_TO_START, range.get(), ec) < 1) { + if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1) + // The original range contains r. + selection->setSelection(VisibleSelection(range.get())); + else + // The original range and r intersect. + selection->setSelection(VisibleSelection(range->startPosition(), r->endPosition(), DOWNSTREAM)); + } + } +} + +void DOMSelection::deleteFromDocument() +{ + if (!m_frame) + return; + + SelectionController* selection = m_frame->selection(); + + if (selection->isNone()) + return; + + if (isCollapsed()) + selection->modify(SelectionController::AlterationExtend, DirectionBackward, CharacterGranularity); + + RefPtr<Range> selectedRange = selection->selection().toNormalizedRange(); + if (!selectedRange) + return; + + ExceptionCode ec = 0; + selectedRange->deleteContents(ec); + ASSERT(!ec); + + setBaseAndExtent(selectedRange->startContainer(ec), selectedRange->startOffset(ec), selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec); + ASSERT(!ec); +} + +bool DOMSelection::containsNode(const Node* n, bool allowPartial) const +{ + if (!m_frame) + return false; + + SelectionController* selection = m_frame->selection(); + + if (!n || m_frame->document() != n->document() || selection->isNone()) + return false; + + ContainerNode* parentNode = n->parentNode(); + unsigned nodeIndex = n->nodeIndex(); + RefPtr<Range> selectedRange = selection->selection().toNormalizedRange(); + + if (!parentNode) + return false; + + ExceptionCode ec = 0; + bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(ec), selectedRange->startOffset(ec)) >= 0 + && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(ec), selectedRange->endOffset(ec)) <= 0; + ASSERT(!ec); + if (nodeFullySelected) + return true; + + bool nodeFullyUnselected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->endContainer(ec), selectedRange->endOffset(ec)) > 0 + || Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->startContainer(ec), selectedRange->startOffset(ec)) < 0; + ASSERT(!ec); + if (nodeFullyUnselected) + return false; + + return allowPartial || n->isTextNode(); +} + +void DOMSelection::selectAllChildren(Node* n, ExceptionCode& ec) +{ + if (!n) + return; + + // This doesn't (and shouldn't) select text node characters. + setBaseAndExtent(n, 0, n, n->childNodeCount(), ec); +} + +String DOMSelection::toString() +{ + if (!m_frame) + return String(); + + return plainText(m_frame->selection()->selection().toNormalizedRange().get()); +} + +bool DOMSelection::isValidForPosition(Node* node) const +{ + ASSERT(m_frame); + if (!node) + return true; + return node->document() == m_frame->document(); +} + +} // namespace WebCore |