1/*
2 * Copyright (C) 2007, 2009, 2016 Apple Inc. All rights reserved.
3 * Copyright (C) 2012 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
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.
17 *
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.
28 */
29
30
31#include "config.h"
32#include "DOMSelection.h"
33
34#include "Document.h"
35#include "Editing.h"
36#include "Frame.h"
37#include "FrameSelection.h"
38#include "Range.h"
39#include "TextIterator.h"
40
41namespace WebCore {
42
43static Node* selectionShadowAncestor(Frame& frame)
44{
45 auto* node = frame.selection().selection().base().anchorNode();
46 if (!node)
47 return nullptr;
48 if (!node->isInShadowTree())
49 return nullptr;
50 // FIXME: Unclear on why this needs to be the possibly null frame.document() instead of the never null node->document().
51 return frame.document()->ancestorNodeInThisScope(node);
52}
53
54DOMSelection::DOMSelection(DOMWindow& window)
55 : DOMWindowProperty(&window)
56{
57}
58
59const VisibleSelection& DOMSelection::visibleSelection() const
60{
61 ASSERT(frame());
62 return frame()->selection().selection();
63}
64
65static Position anchorPosition(const VisibleSelection& selection)
66{
67 auto anchor = selection.isBaseFirst() ? selection.start() : selection.end();
68 return anchor.parentAnchoredEquivalent();
69}
70
71static Position focusPosition(const VisibleSelection& selection)
72{
73 auto focus = selection.isBaseFirst() ? selection.end() : selection.start();
74 return focus.parentAnchoredEquivalent();
75}
76
77static Position basePosition(const VisibleSelection& selection)
78{
79 return selection.base().parentAnchoredEquivalent();
80}
81
82static Position extentPosition(const VisibleSelection& selection)
83{
84 return selection.extent().parentAnchoredEquivalent();
85}
86
87Node* DOMSelection::anchorNode() const
88{
89 if (!frame())
90 return nullptr;
91 return shadowAdjustedNode(anchorPosition(visibleSelection()));
92}
93
94unsigned DOMSelection::anchorOffset() const
95{
96 if (!frame())
97 return 0;
98 return shadowAdjustedOffset(anchorPosition(visibleSelection()));
99}
100
101Node* DOMSelection::focusNode() const
102{
103 if (!frame())
104 return nullptr;
105 return shadowAdjustedNode(focusPosition(visibleSelection()));
106}
107
108unsigned DOMSelection::focusOffset() const
109{
110 if (!frame())
111 return 0;
112 return shadowAdjustedOffset(focusPosition(visibleSelection()));
113}
114
115Node* DOMSelection::baseNode() const
116{
117 if (!frame())
118 return nullptr;
119 return shadowAdjustedNode(basePosition(visibleSelection()));
120}
121
122unsigned DOMSelection::baseOffset() const
123{
124 if (!frame())
125 return 0;
126 return shadowAdjustedOffset(basePosition(visibleSelection()));
127}
128
129Node* DOMSelection::extentNode() const
130{
131 if (!frame())
132 return nullptr;
133 return shadowAdjustedNode(extentPosition(visibleSelection()));
134}
135
136unsigned DOMSelection::extentOffset() const
137{
138 if (!frame())
139 return 0;
140 return shadowAdjustedOffset(extentPosition(visibleSelection()));
141}
142
143bool DOMSelection::isCollapsed() const
144{
145 auto* frame = this->frame();
146 if (!frame || selectionShadowAncestor(*frame))
147 return true;
148 return !frame->selection().isRange();
149}
150
151String DOMSelection::type() const
152{
153 auto* frame = this->frame();
154 if (!frame)
155 return "None"_s;
156 auto& selection = frame->selection();
157 if (selection.isNone())
158 return "None"_s;
159 if (selection.isCaret())
160 return "Caret"_s;
161 return "Range"_s;
162}
163
164unsigned DOMSelection::rangeCount() const
165{
166 auto* frame = this->frame();
167 return !frame || frame->selection().isNone() ? 0 : 1;
168}
169
170void DOMSelection::collapse(Node* node, unsigned offset)
171{
172 if (!isValidForPosition(node))
173 return;
174
175 Ref<Frame> protectedFrame(*frame());
176 protectedFrame->selection().moveTo(createLegacyEditingPosition(node, offset), DOWNSTREAM);
177}
178
179ExceptionOr<void> DOMSelection::collapseToEnd()
180{
181 auto* frame = this->frame();
182 if (!frame)
183 return { };
184 auto& selection = frame->selection();
185 if (selection.isNone())
186 return Exception { InvalidStateError };
187
188 Ref<Frame> protector(*frame);
189 selection.moveTo(selection.selection().end(), DOWNSTREAM);
190 return { };
191}
192
193ExceptionOr<void> DOMSelection::collapseToStart()
194{
195 auto* frame = this->frame();
196 if (!frame)
197 return { };
198 auto& selection = frame->selection();
199 if (selection.isNone())
200 return Exception { InvalidStateError };
201
202 Ref<Frame> protector(*frame);
203 selection.moveTo(selection.selection().start(), DOWNSTREAM);
204 return { };
205}
206
207void DOMSelection::empty()
208{
209 auto* frame = this->frame();
210 if (!frame)
211 return;
212 frame->selection().clear();
213}
214
215void DOMSelection::setBaseAndExtent(Node* baseNode, unsigned baseOffset, Node* extentNode, unsigned extentOffset)
216{
217 if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode))
218 return;
219
220 Ref<Frame> protectedFrame(*frame());
221 protectedFrame->selection().moveTo(createLegacyEditingPosition(baseNode, baseOffset), createLegacyEditingPosition(extentNode, extentOffset), DOWNSTREAM);
222}
223
224void DOMSelection::setPosition(Node* node, unsigned offset)
225{
226 if (!isValidForPosition(node))
227 return;
228
229 Ref<Frame> protectedFrame(*frame());
230 protectedFrame->selection().moveTo(createLegacyEditingPosition(node, offset), DOWNSTREAM);
231}
232
233void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString)
234{
235 auto* frame = this->frame();
236 if (!frame)
237 return;
238
239 FrameSelection::EAlteration alter;
240 if (equalLettersIgnoringASCIICase(alterString, "extend"))
241 alter = FrameSelection::AlterationExtend;
242 else if (equalLettersIgnoringASCIICase(alterString, "move"))
243 alter = FrameSelection::AlterationMove;
244 else
245 return;
246
247 SelectionDirection direction;
248 if (equalLettersIgnoringASCIICase(directionString, "forward"))
249 direction = DirectionForward;
250 else if (equalLettersIgnoringASCIICase(directionString, "backward"))
251 direction = DirectionBackward;
252 else if (equalLettersIgnoringASCIICase(directionString, "left"))
253 direction = DirectionLeft;
254 else if (equalLettersIgnoringASCIICase(directionString, "right"))
255 direction = DirectionRight;
256 else
257 return;
258
259 TextGranularity granularity;
260 if (equalLettersIgnoringASCIICase(granularityString, "character"))
261 granularity = CharacterGranularity;
262 else if (equalLettersIgnoringASCIICase(granularityString, "word"))
263 granularity = WordGranularity;
264 else if (equalLettersIgnoringASCIICase(granularityString, "sentence"))
265 granularity = SentenceGranularity;
266 else if (equalLettersIgnoringASCIICase(granularityString, "line"))
267 granularity = LineGranularity;
268 else if (equalLettersIgnoringASCIICase(granularityString, "paragraph"))
269 granularity = ParagraphGranularity;
270 else if (equalLettersIgnoringASCIICase(granularityString, "lineboundary"))
271 granularity = LineBoundary;
272 else if (equalLettersIgnoringASCIICase(granularityString, "sentenceboundary"))
273 granularity = SentenceBoundary;
274 else if (equalLettersIgnoringASCIICase(granularityString, "paragraphboundary"))
275 granularity = ParagraphBoundary;
276 else if (equalLettersIgnoringASCIICase(granularityString, "documentboundary"))
277 granularity = DocumentBoundary;
278 else
279 return;
280
281 Ref<Frame> protector(*frame);
282 frame->selection().modify(alter, direction, granularity);
283}
284
285ExceptionOr<void> DOMSelection::extend(Node& node, unsigned offset)
286{
287 auto* frame = this->frame();
288 if (!frame)
289 return { };
290 if (offset > (node.isCharacterDataNode() ? caretMaxOffset(node) : node.countChildNodes()))
291 return Exception { IndexSizeError };
292 if (!isValidForPosition(&node))
293 return { };
294
295 Ref<Frame> protector(*frame);
296 frame->selection().setExtent(createLegacyEditingPosition(&node, offset), DOWNSTREAM);
297 return { };
298}
299
300ExceptionOr<Ref<Range>> DOMSelection::getRangeAt(unsigned index)
301{
302 if (index >= rangeCount())
303 return Exception { IndexSizeError };
304
305 // If you're hitting this, you've added broken multi-range selection support.
306 ASSERT(rangeCount() == 1);
307
308 auto* frame = this->frame();
309 if (auto* shadowAncestor = selectionShadowAncestor(*frame)) {
310 auto* container = shadowAncestor->parentNodeGuaranteedHostFree();
311 unsigned offset = shadowAncestor->computeNodeIndex();
312 return Range::create(shadowAncestor->document(), container, offset, container, offset);
313 }
314
315 auto firstRange = frame->selection().selection().firstRange();
316 ASSERT(firstRange);
317 if (!firstRange)
318 return Exception { IndexSizeError };
319 return firstRange.releaseNonNull();
320}
321
322void DOMSelection::removeAllRanges()
323{
324 auto* frame = this->frame();
325 if (!frame)
326 return;
327 frame->selection().clear();
328}
329
330void DOMSelection::addRange(Range& range)
331{
332 auto* frame = this->frame();
333 if (!frame)
334 return;
335
336 Ref<Frame> protector(*frame);
337
338 auto& selection = frame->selection();
339 if (selection.isNone()) {
340 selection.moveTo(&range);
341 return;
342 }
343
344 auto normalizedRange = selection.selection().toNormalizedRange();
345 if (!normalizedRange)
346 return;
347
348 auto result = range.compareBoundaryPoints(Range::START_TO_START, *normalizedRange);
349 if (!result.hasException() && result.releaseReturnValue() == -1) {
350 // We don't support discontiguous selection. We don't do anything if the two ranges don't intersect.
351 result = range.compareBoundaryPoints(Range::START_TO_END, *normalizedRange);
352 if (!result.hasException() && result.releaseReturnValue() > -1) {
353 result = range.compareBoundaryPoints(Range::END_TO_END, *normalizedRange);
354 if (!result.hasException() && result.releaseReturnValue() == -1) {
355 // The ranges intersect.
356 selection.moveTo(range.startPosition(), normalizedRange->endPosition(), DOWNSTREAM);
357 } else {
358 // The new range contains the original range.
359 selection.moveTo(&range);
360 }
361 }
362 } else {
363 // We don't support discontiguous selection. We don't do anything if the two ranges don't intersect.
364 result = range.compareBoundaryPoints(Range::END_TO_START, *normalizedRange);
365 if (!result.hasException() && result.releaseReturnValue() < 1) {
366 result = range.compareBoundaryPoints(Range::END_TO_END, *normalizedRange);
367 if (!result.hasException() && result.releaseReturnValue() == -1) {
368 // The original range contains the new range.
369 selection.moveTo(normalizedRange.get());
370 } else {
371 // The ranges intersect.
372 selection.moveTo(normalizedRange->startPosition(), range.endPosition(), DOWNSTREAM);
373 }
374 }
375 }
376}
377
378void DOMSelection::deleteFromDocument()
379{
380 auto* frame = this->frame();
381 if (!frame)
382 return;
383
384 auto& selection = frame->selection();
385 if (selection.isNone())
386 return;
387
388 auto selectedRange = selection.selection().toNormalizedRange();
389 if (!selectedRange || selectedRange->shadowRoot())
390 return;
391
392 Ref<Frame> protector(*frame);
393 selectedRange->deleteContents();
394 setBaseAndExtent(&selectedRange->startContainer(), selectedRange->startOffset(), &selectedRange->startContainer(), selectedRange->startOffset());
395}
396
397bool DOMSelection::containsNode(Node& node, bool allowPartial) const
398{
399 auto* frame = this->frame();
400 if (!frame)
401 return false;
402
403 auto& selection = frame->selection();
404 if (frame->document() != &node.document() || selection.isNone())
405 return false;
406
407 Ref<Node> protectedNode(node);
408 auto selectedRange = selection.selection().toNormalizedRange();
409 if (!selectedRange)
410 return false;
411
412 ContainerNode* parentNode = node.parentNode();
413 if (!parentNode || !parentNode->isConnected())
414 return false;
415 unsigned nodeIndex = node.computeNodeIndex();
416
417 auto startsResult = Range::compareBoundaryPoints(parentNode, nodeIndex, &selectedRange->startContainer(), selectedRange->startOffset());
418 if (startsResult.hasException())
419 return false;
420
421 auto endsResult = Range::compareBoundaryPoints(parentNode, nodeIndex + 1, &selectedRange->endContainer(), selectedRange->endOffset());
422 ASSERT(!endsResult.hasException());
423 bool isNodeFullySelected = !startsResult.hasException() && startsResult.releaseReturnValue() >= 0
424 && !endsResult.hasException() && endsResult.releaseReturnValue() <= 0;
425 if (isNodeFullySelected)
426 return true;
427
428 auto startEndResult = Range::compareBoundaryPoints(parentNode, nodeIndex, &selectedRange->endContainer(), selectedRange->endOffset());
429 ASSERT(!startEndResult.hasException());
430 auto endStartResult = Range::compareBoundaryPoints(parentNode, nodeIndex + 1, &selectedRange->startContainer(), selectedRange->startOffset());
431 ASSERT(!endStartResult.hasException());
432 bool isNodeFullyUnselected = (!startEndResult.hasException() && startEndResult.releaseReturnValue() > 0)
433 || (!endStartResult.hasException() && endStartResult.releaseReturnValue() < 0);
434 if (isNodeFullyUnselected)
435 return false;
436
437 return allowPartial || node.isTextNode();
438}
439
440void DOMSelection::selectAllChildren(Node& node)
441{
442 // This doesn't (and shouldn't) select text node characters.
443 setBaseAndExtent(&node, 0, &node, node.countChildNodes());
444}
445
446String DOMSelection::toString()
447{
448 auto* frame = this->frame();
449 if (!frame)
450 return String();
451 return plainText(frame->selection().selection().toNormalizedRange().get());
452}
453
454Node* DOMSelection::shadowAdjustedNode(const Position& position) const
455{
456 if (position.isNull())
457 return nullptr;
458
459 auto* containerNode = position.containerNode();
460 auto* adjustedNode = frame()->document()->ancestorNodeInThisScope(containerNode);
461 if (!adjustedNode)
462 return nullptr;
463
464 if (containerNode == adjustedNode)
465 return containerNode;
466
467 return adjustedNode->parentNodeGuaranteedHostFree();
468}
469
470unsigned DOMSelection::shadowAdjustedOffset(const Position& position) const
471{
472 if (position.isNull())
473 return 0;
474
475 auto* containerNode = position.containerNode();
476 auto* adjustedNode = frame()->document()->ancestorNodeInThisScope(containerNode);
477 if (!adjustedNode)
478 return 0;
479
480 if (containerNode == adjustedNode)
481 return position.computeOffsetInContainerNode();
482
483 return adjustedNode->computeNodeIndex();
484}
485
486bool DOMSelection::isValidForPosition(Node* node) const
487{
488 auto* frame = this->frame();
489 if (!frame)
490 return false;
491 if (!node)
492 return true;
493 return &node->document() == frame->document();
494}
495
496} // namespace WebCore
497