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 "DOMSelection.h"
35
#include "ExceptionCode.h"
37
#include "FrameSelection.h"
40
#include "TextIterator.h"
41
#include "TreeScope.h"
42
#include "htmlediting.h"
43
#include <wtf/text/WTFString.h>
47
static Node* selectionShadowAncestor(Frame* frame)
49
Node* node = frame->selection()->selection().base().anchorNode();
53
if (!node->isInShadowTree())
56
return frame->document()->ancestorInThisScope(node);
59
DOMSelection::DOMSelection(const TreeScope* treeScope)
60
: DOMWindowProperty(treeScope->rootNode()->document()->frame())
61
, m_treeScope(treeScope)
65
void DOMSelection::clearTreeScope()
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
103
return shadowAdjustedNode(anchorPosition(visibleSelection()));
106
int DOMSelection::anchorOffset() const
111
return shadowAdjustedOffset(anchorPosition(visibleSelection()));
114
Node* DOMSelection::focusNode() const
119
return shadowAdjustedNode(focusPosition(visibleSelection()));
122
int DOMSelection::focusOffset() const
127
return shadowAdjustedOffset(focusPosition(visibleSelection()));
130
Node* DOMSelection::baseNode() const
135
return shadowAdjustedNode(basePosition(visibleSelection()));
138
int DOMSelection::baseOffset() const
143
return shadowAdjustedOffset(basePosition(visibleSelection()));
146
Node* DOMSelection::extentNode() const
151
return shadowAdjustedNode(extentPosition(visibleSelection()));
154
int DOMSelection::extentOffset() const
159
return shadowAdjustedOffset(extentPosition(visibleSelection()));
162
bool DOMSelection::isCollapsed() const
164
if (!m_frame || selectionShadowAncestor(m_frame))
166
return !m_frame->selection()->isRange();
169
String DOMSelection::type() const
174
FrameSelection* selection = m_frame->selection();
176
// This is a WebKit DOM extension, incompatible with an IE extension
177
// IE has this same attribute, but returns "none", "text" and "control"
178
// http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx
179
if (selection->isNone())
181
if (selection->isCaret())
186
int DOMSelection::rangeCount() const
190
return m_frame->selection()->isNone() ? 0 : 1;
193
void DOMSelection::collapse(Node* node, int offset, ExceptionCode& ec)
203
if (!isValidForPosition(node))
206
// FIXME: Eliminate legacy editing positions
207
m_frame->selection()->moveTo(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
210
void DOMSelection::collapseToEnd(ExceptionCode& ec)
215
const VisibleSelection& selection = m_frame->selection()->selection();
217
if (selection.isNone()) {
218
ec = INVALID_STATE_ERR;
222
m_frame->selection()->moveTo(VisiblePosition(selection.end(), DOWNSTREAM));
225
void DOMSelection::collapseToStart(ExceptionCode& ec)
230
const VisibleSelection& selection = m_frame->selection()->selection();
232
if (selection.isNone()) {
233
ec = INVALID_STATE_ERR;
237
m_frame->selection()->moveTo(VisiblePosition(selection.start(), DOWNSTREAM));
240
void DOMSelection::empty()
244
m_frame->selection()->clear();
247
void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionCode& ec)
252
if (baseOffset < 0 || extentOffset < 0) {
257
if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode))
260
// FIXME: Eliminate legacy editing positions
261
VisiblePosition visibleBase = VisiblePosition(createLegacyEditingPosition(baseNode, baseOffset), DOWNSTREAM);
262
VisiblePosition visibleExtent = VisiblePosition(createLegacyEditingPosition(extentNode, extentOffset), DOWNSTREAM);
264
m_frame->selection()->moveTo(visibleBase, visibleExtent);
267
void DOMSelection::setPosition(Node* node, int offset, ExceptionCode& ec)
276
if (!isValidForPosition(node))
279
// FIXME: Eliminate legacy editing positions
280
m_frame->selection()->moveTo(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
283
void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString)
288
FrameSelection::EAlteration alter;
289
if (equalIgnoringCase(alterString, "extend"))
290
alter = FrameSelection::AlterationExtend;
291
else if (equalIgnoringCase(alterString, "move"))
292
alter = FrameSelection::AlterationMove;
296
SelectionDirection direction;
297
if (equalIgnoringCase(directionString, "forward"))
298
direction = DirectionForward;
299
else if (equalIgnoringCase(directionString, "backward"))
300
direction = DirectionBackward;
301
else if (equalIgnoringCase(directionString, "left"))
302
direction = DirectionLeft;
303
else if (equalIgnoringCase(directionString, "right"))
304
direction = DirectionRight;
308
TextGranularity granularity;
309
if (equalIgnoringCase(granularityString, "character"))
310
granularity = CharacterGranularity;
311
else if (equalIgnoringCase(granularityString, "word"))
312
granularity = WordGranularity;
313
else if (equalIgnoringCase(granularityString, "sentence"))
314
granularity = SentenceGranularity;
315
else if (equalIgnoringCase(granularityString, "line"))
316
granularity = LineGranularity;
317
else if (equalIgnoringCase(granularityString, "paragraph"))
318
granularity = ParagraphGranularity;
319
else if (equalIgnoringCase(granularityString, "lineboundary"))
320
granularity = LineBoundary;
321
else if (equalIgnoringCase(granularityString, "sentenceboundary"))
322
granularity = SentenceBoundary;
323
else if (equalIgnoringCase(granularityString, "paragraphboundary"))
324
granularity = ParagraphBoundary;
325
else if (equalIgnoringCase(granularityString, "documentboundary"))
326
granularity = DocumentBoundary;
330
m_frame->selection()->modify(alter, direction, granularity);
333
void DOMSelection::extend(Node* node, int offset, ExceptionCode& ec)
339
ec = TYPE_MISMATCH_ERR;
343
if (offset < 0 || offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->childNodeCount())) {
348
if (!isValidForPosition(node))
351
// FIXME: Eliminate legacy editing positions
352
m_frame->selection()->setExtent(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
355
PassRefPtr<Range> DOMSelection::getRangeAt(int index, ExceptionCode& ec)
360
if (index < 0 || index >= rangeCount()) {
365
// If you're hitting this, you've added broken multi-range selection support
366
ASSERT(rangeCount() == 1);
368
if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) {
369
ContainerNode* container = shadowAncestor->parentNodeGuaranteedHostFree();
370
int offset = shadowAncestor->nodeIndex();
371
return Range::create(shadowAncestor->document(), container, offset, container, offset);
374
const VisibleSelection& selection = m_frame->selection()->selection();
375
return selection.firstRange();
378
void DOMSelection::removeAllRanges()
382
m_frame->selection()->clear();
385
void DOMSelection::addRange(Range* r)
392
FrameSelection* selection = m_frame->selection();
394
if (selection->isNone()) {
395
selection->setSelection(VisibleSelection(r));
399
RefPtr<Range> range = selection->selection().toNormalizedRange();
400
ExceptionCode ec = 0;
401
if (r->compareBoundaryPoints(Range::START_TO_START, range.get(), ec) == -1) {
402
// We don't support discontiguous selection. We don't do anything if r and range don't intersect.
403
if (r->compareBoundaryPoints(Range::START_TO_END, range.get(), ec) > -1) {
404
if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1)
405
// The original range and r intersect.
406
selection->setSelection(VisibleSelection(r->startPosition(), range->endPosition(), DOWNSTREAM));
408
// r contains the original range.
409
selection->setSelection(VisibleSelection(r));
412
// We don't support discontiguous selection. We don't do anything if r and range don't intersect.
413
if (r->compareBoundaryPoints(Range::END_TO_START, range.get(), ec) < 1 && !ec) {
414
if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1)
415
// The original range contains r.
416
selection->setSelection(VisibleSelection(range.get()));
418
// The original range and r intersect.
419
selection->setSelection(VisibleSelection(range->startPosition(), r->endPosition(), DOWNSTREAM));
424
void DOMSelection::deleteFromDocument()
429
FrameSelection* selection = m_frame->selection();
431
if (selection->isNone())
435
selection->modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity);
437
RefPtr<Range> selectedRange = selection->selection().toNormalizedRange();
441
ExceptionCode ec = 0;
442
selectedRange->deleteContents(ec);
445
setBaseAndExtent(selectedRange->startContainer(ec), selectedRange->startOffset(ec), selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec);
449
bool DOMSelection::containsNode(const Node* n, bool allowPartial) const
454
FrameSelection* selection = m_frame->selection();
456
if (!n || m_frame->document() != n->document() || selection->isNone())
459
ContainerNode* parentNode = n->parentNode();
460
unsigned nodeIndex = n->nodeIndex();
461
RefPtr<Range> selectedRange = selection->selection().toNormalizedRange();
466
ExceptionCode ec = 0;
467
bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec) >= 0 && !ec
468
&& Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(ec), selectedRange->endOffset(ec), ec) <= 0 && !ec;
470
if (nodeFullySelected)
473
bool nodeFullyUnselected = (Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->endContainer(ec), selectedRange->endOffset(ec), ec) > 0 && !ec)
474
|| (Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec) < 0 && !ec);
476
if (nodeFullyUnselected)
479
return allowPartial || n->isTextNode();
482
void DOMSelection::selectAllChildren(Node* n, ExceptionCode& ec)
487
// This doesn't (and shouldn't) select text node characters.
488
setBaseAndExtent(n, 0, n, n->childNodeCount(), ec);
491
String DOMSelection::toString()
496
return plainText(m_frame->selection()->selection().toNormalizedRange().get());
499
Node* DOMSelection::shadowAdjustedNode(const Position& position) const
501
if (position.isNull())
504
Node* containerNode = position.containerNode();
505
Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
510
if (containerNode == adjustedNode)
511
return containerNode;
513
return adjustedNode->parentNodeGuaranteedHostFree();
516
int DOMSelection::shadowAdjustedOffset(const Position& position) const
518
if (position.isNull())
521
Node* containerNode = position.containerNode();
522
Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
527
if (containerNode == adjustedNode)
528
return position.computeOffsetInContainerNode();
530
return adjustedNode->nodeIndex();
533
bool DOMSelection::isValidForPosition(Node* node) const
538
return node->document() == m_frame->document();
541
} // namespace WebCore